init
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
||||
3
.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
1
.example.env
Normal file
@@ -0,0 +1 @@
|
||||
NEXT_PUBLIC_BACKEND_BASE_URL=string
|
||||
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
/.idea
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
.env
|
||||
20
.prettierrc
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 140,
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"insertPragma": false,
|
||||
"jsxBracketSameLine": false,
|
||||
"proseWrap": "preserve",
|
||||
"quoteProps": "as-needed",
|
||||
"requirePragma": false,
|
||||
"useTabs": false,
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"rangeStart": 0
|
||||
}
|
||||
22
Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
||||
# Use an official Node.js runtime as the base image
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the package.json and package-lock.json to the working directory
|
||||
COPY package*.json ./
|
||||
|
||||
# Install npm dependencies
|
||||
RUN npm install --force
|
||||
|
||||
# Copy the rest of your application code to the working directory
|
||||
COPY . .
|
||||
|
||||
# Expose the port your application is listening on (if any)
|
||||
EXPOSE 3000
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Start the application with "npm run dev"
|
||||
CMD ["npm", "run", "start"]
|
||||
22
Dockerfile-dev
Normal file
@@ -0,0 +1,22 @@
|
||||
# Use an official Node.js runtime as the base image
|
||||
FROM node:18-alpine AS base
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the package.json and package-lock.json to the working directory
|
||||
COPY package*.json ./
|
||||
|
||||
# Install npm dependencies
|
||||
RUN npm install --force
|
||||
|
||||
# Copy the rest of your application code to the working directory
|
||||
COPY . .
|
||||
|
||||
# Expose the port your application is listening on (if any)
|
||||
EXPOSE 3000
|
||||
|
||||
RUN npm run build:dev
|
||||
|
||||
# Start the application with "npm run dev"
|
||||
CMD ["npm", "run", "start"]
|
||||
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# C-POST
|
||||
|
||||
## Example app router with react-query
|
||||
https://github.com/wpcodevo/nextjs13-react-query/blob/main/src/app/hydration/page.tsx
|
||||
|
||||
## DOCKER BUILD IMAGE
|
||||
### docker build -t cpost-front .
|
||||
|
||||
## DOCKER RUN IMAGE
|
||||
### docker run -p 3000:3000 cpost-front
|
||||
17
dc-front-dev.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-dev
|
||||
image: cpost_frontend_dev
|
||||
container_name: cpost_frontend_dev
|
||||
expose:
|
||||
- "3000"
|
||||
networks:
|
||||
cpost-network:
|
||||
ipv4_address: 10.10.0.16
|
||||
restart: "always"
|
||||
networks:
|
||||
cpost-network:
|
||||
name: cpostnetwork
|
||||
external: true
|
||||
16
dc-front.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: cpost_frontend
|
||||
expose:
|
||||
- "3000"
|
||||
networks:
|
||||
cpost-network:
|
||||
ipv4_address: 10.10.0.4
|
||||
restart: "always"
|
||||
networks:
|
||||
cpost-network:
|
||||
name: cpostnetwork
|
||||
external: true
|
||||
8
environment.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: 'production' | 'development' | 'test';
|
||||
|
||||
NEXT_PUBLIC_BASE_URL: string;
|
||||
NEXT_PUBLIC_BACKEND_BASE_URL: string;
|
||||
}
|
||||
}
|
||||
11
i18n.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { getRequestConfig } from 'next-intl/server';
|
||||
import { locales } from '@/helpers/constants';
|
||||
|
||||
export default getRequestConfig(async ({ locale }) => {
|
||||
if (!locales.includes(locale as any)) notFound();
|
||||
|
||||
return {
|
||||
messages: (await import(`./messages/${locale}.json`)).default,
|
||||
};
|
||||
});
|
||||
225
messages/cn.json
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"main_page": "主页",
|
||||
"home": "首页",
|
||||
"services": "服务",
|
||||
"news": "新闻",
|
||||
"about_us": "关于我们",
|
||||
"tariffs": "收费标准",
|
||||
"contacts": "联系我们",
|
||||
"check_order": "查看订单",
|
||||
"hero_title": "从中国运送货物到乌兹别克斯坦",
|
||||
"hero_subtitle": "您想知道如何将您的购物从中国运送到乌兹别克斯坦吗?",
|
||||
"good_quality": "高质量",
|
||||
"fast": "快速",
|
||||
"online_watching": "在线跟踪",
|
||||
"safe_warehouse": "安全仓库",
|
||||
"we_proud_to_serve_global": "我们以为全球提供最佳的货运和运输解决方案感到自豪。",
|
||||
"our_services": "我们的服务",
|
||||
|
||||
"air_transport": "空运",
|
||||
"air_transport__subtitle": "CPost提供各类大型和重型货物的空运服务。",
|
||||
"car_transport": "汽车运输",
|
||||
"car_transport__subtitle": "传统汽车运输,价格便宜,灵活性强。",
|
||||
"water_transport": "水运",
|
||||
"water_transport__subtitle": "CPost专注于海运货物。",
|
||||
"railway_transport": "铁路运输",
|
||||
"railway_transport__subtitle": "铁路运输是CPost主要业务方向之一。公司提供所需吨位的集装箱和各类货车运输。",
|
||||
"warehouse_yivu_and_guanchjou": "义乌和广州仓储服务",
|
||||
"warehouse_yivu_and_guanchjou__subtitle": "CPost在中国提供仓储物流服务,与从中国运输的车辆同时进行。",
|
||||
"customs_office_service": "海关清关服务",
|
||||
"customs_office_service__subtitle": "CPost重视客户的时间,并提供货物的海关申报服务。按照HS和TN编码对货物进行分类。",
|
||||
|
||||
"pricing": {
|
||||
"title": "价格",
|
||||
"limit": "交货时间:",
|
||||
"min_weight": "最小重量",
|
||||
"more": "更多详情",
|
||||
|
||||
"pricing1": {
|
||||
"title": "空运",
|
||||
"price": "每千克从$10起",
|
||||
"limit": "7-14天",
|
||||
"min_weight": "1千克"
|
||||
},
|
||||
"pricing2": {
|
||||
"title": "快递汽车运输",
|
||||
"price": "每千克从$8起",
|
||||
"limit": "30天",
|
||||
"min_weight": "1千克"
|
||||
},
|
||||
"pricing3": {
|
||||
"title": "汽车运输",
|
||||
"price": "按协议定价",
|
||||
"limit": "20-25天",
|
||||
"min_weight": "1立方米"
|
||||
},
|
||||
"pricing4": {
|
||||
"title": "铁路运输",
|
||||
"price": "按协议定价",
|
||||
"limit": "35-40天",
|
||||
"min_weight": "1立方米"
|
||||
}
|
||||
},
|
||||
|
||||
"our_partners": "我们的合作伙伴",
|
||||
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
"contacts_subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
|
||||
"address": "地址",
|
||||
"email": "电子邮件",
|
||||
"phone": "电话",
|
||||
"telegram": "Telegram",
|
||||
"instagram": "Instagram",
|
||||
"real_address": "塔什干市,奥尔马佐尔区,_______街",
|
||||
|
||||
"faq": {
|
||||
"title": "常见问题",
|
||||
"subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
|
||||
"faq1": {
|
||||
"question": "我如何实时追踪我的货物?",
|
||||
"answer": "大多数物流公司通过其网站或移动应用提供在线追踪服务。您可以输入跟踪号以获取货物的实时位置和状态更新。"
|
||||
}
|
||||
},
|
||||
|
||||
"footer_site_info_text": "可靠且快速的物流服务提供商",
|
||||
"privacy_policy": "隐私政策",
|
||||
"terms_of_use": "使用条款",
|
||||
"sign_up_to_tg": "注册以获取最新消息、公司见解和CPost更新。",
|
||||
"your_email": "您的电子邮件",
|
||||
"pages": "页面",
|
||||
"info": "信息",
|
||||
"join_tg": "加入我们的Telegram",
|
||||
"all_rights_reserved": "版权所有",
|
||||
|
||||
"calc_obj": {
|
||||
"title": "计算器",
|
||||
"subtitle": "选择您的订单的运输类型和重量,以了解运费。",
|
||||
"colvo_sht": "数量(件)",
|
||||
"volume": "体积(立方米)",
|
||||
"weight_of_product": "货物重量...",
|
||||
"calc_value": "计算"
|
||||
},
|
||||
"check_order_obj": {
|
||||
"title": "包裹追踪",
|
||||
"subtitle": "输入从在线商店收到的包裹条形码。",
|
||||
"enter_check_code": "输入条形码...",
|
||||
"select_delivery_type": "选择运输类型",
|
||||
"delivery_type_avia": "航空",
|
||||
"delivery_type_car": "汽车"
|
||||
},
|
||||
"price": "价格",
|
||||
|
||||
"__dash": "------------------------------------DASHBOARD------------------------------------------------------",
|
||||
"dashboard": "仪表板",
|
||||
"parties": "派对",
|
||||
"create_party": "创建派对",
|
||||
"update_party": "更新派对",
|
||||
"create_box": "创建箱子",
|
||||
"create_packet": "創建包",
|
||||
"update_box": "更新箱子",
|
||||
"update_package": "程式包更新",
|
||||
"update_item": "更新物品",
|
||||
"no": "编号",
|
||||
"id": "ID",
|
||||
"name": "名称",
|
||||
"party_status": "派对状态",
|
||||
"box_status": "箱子状态",
|
||||
"actions": "操作",
|
||||
"reset_filter": "重置过滤器",
|
||||
"search": "搜索",
|
||||
"boxes": "套餐",
|
||||
"packet": "套餐",
|
||||
"filter": "过滤器",
|
||||
"party_name": "派对名称",
|
||||
"box_type": "箱子类型",
|
||||
"box_number": "箱子编号",
|
||||
"net_weight": "净重",
|
||||
"gross_weight": "毛重",
|
||||
"box_size": "箱子大小",
|
||||
"box_weight": "箱子重量",
|
||||
"volume": "体积",
|
||||
"netto": "净重",
|
||||
"brutto": "毛重",
|
||||
"description": "描述",
|
||||
"add_product": "添加产品",
|
||||
"add_box": "添加箱子",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"cargo_id": "貨物編號",
|
||||
"please_enter_use_id": "请输入使用ID",
|
||||
"quantity": "数量",
|
||||
"weight": "重量",
|
||||
"add_more": "添加更多",
|
||||
"create": "创建",
|
||||
"cancel": "取消",
|
||||
"update": "更新",
|
||||
"clients": "客户",
|
||||
"client": "客戶",
|
||||
"create_client": "创建客户",
|
||||
"client_name": "客户名称",
|
||||
"fullName": "全名",
|
||||
"products": "产品",
|
||||
"product_name": "产品名称",
|
||||
"track_id": "跟踪ID",
|
||||
"party": "派对",
|
||||
"staffs": "员工",
|
||||
"create_staff": "创建员工",
|
||||
"username": "用户名",
|
||||
"role": "角色",
|
||||
"status": "状态",
|
||||
"active": "活跃",
|
||||
"not_active": "非活跃",
|
||||
"log_out": "登出",
|
||||
"password": "密码",
|
||||
"create_invoice": "创建发票",
|
||||
"hasInvoice": "有发票",
|
||||
"required": "必需",
|
||||
"total_price": "总价",
|
||||
"not_found": "未找到",
|
||||
"loading": "加载",
|
||||
"count_of_boxes": "箱子数量",
|
||||
"count_of_items": "物品数量",
|
||||
"download_excel": "下载Excel",
|
||||
|
||||
"enter_party_name_to_find": "输入要查找的派对名称...",
|
||||
"enter_box_name_to_find": "输入要查找的箱子名称...",
|
||||
"enter_client_name_to_find": "输入要查找的客户名称...",
|
||||
|
||||
"filter_packet_name": "封包名稱過濾器",
|
||||
|
||||
"filter_party_name": "派对名称过滤器",
|
||||
"filter_box_name": "箱子名称过滤器",
|
||||
"filter_track_id": "跟踪ID过滤器",
|
||||
"filter_client_name": "客户名称过滤器",
|
||||
"filter_item_name": "产品名称过滤器",
|
||||
"filter_by_box_status": "箱子状态过滤器",
|
||||
"filter_by_party_status": "派对状态过滤器",
|
||||
|
||||
"COLLECTING": "收集中",
|
||||
"ON_THE_WAY": "在路上",
|
||||
"ARRIVED": "已到达",
|
||||
"READY_TO_INVOICE": "准备开发票",
|
||||
"READY": "准备",
|
||||
"IN_CUSTOMS": "在海關",
|
||||
"IN_WAREHOUSE": "接觸客戶",
|
||||
"DELIVERED": "發表",
|
||||
|
||||
"add_photo_to_item": "將照片加入項目",
|
||||
"photo": "照片",
|
||||
"summa": "總結",
|
||||
"avia_cargo_id": "航空貨物編號",
|
||||
"auto_cargo_id": "自動貨物編號",
|
||||
"update_client": "更新客戶端",
|
||||
"pinfl": "平弗",
|
||||
"passportSeries": "護照系列",
|
||||
"search_available_in_trekid": "此處您只能透過 TrekID 搜尋產品",
|
||||
"party_total_brutto": "黨的整體粗糙度",
|
||||
"date_of_birth": "出生日期",
|
||||
"are_you_sure_delete_client_id": "您確定要刪除客戶端 {id} 嗎?",
|
||||
"download_all_items": "下載所有項目",
|
||||
"download_all_clients": "下載所有客戶端",
|
||||
"are_you_sure_delete_party": "您确定要删除货物批次{name}吗",
|
||||
"passport": "護照",
|
||||
"weight_of_items": "產品重量",
|
||||
"update_packet": "編輯套件",
|
||||
"party_weight": "大量重量"
|
||||
}
|
||||
224
messages/en.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"main_page": "Main Page",
|
||||
"home": "Home",
|
||||
"services": "Services",
|
||||
"news": "News",
|
||||
"about_us": "About Us",
|
||||
"tariffs": "Tariffs",
|
||||
"contacts": "Contacts",
|
||||
"check_order": "Check Order",
|
||||
"hero_title": "Delivery of Goods from China to Uzbekistan",
|
||||
"hero_subtitle": "Do you wonder how to deliver your purchases from China to Uzbekistan?",
|
||||
"good_quality": "High Quality",
|
||||
"fast": "Fast",
|
||||
"online_watching": "Online Tracking",
|
||||
"safe_warehouse": "Secure Warehouse",
|
||||
"we_proud_to_serve_global": "We take pride in providing the best freight and transportation solutions worldwide.",
|
||||
"our_services": "Our Services",
|
||||
|
||||
"air_transport": "Air Transport",
|
||||
"air_transport__subtitle": "CPost offers air transportation for a wide range of large-sized and heavy goods.",
|
||||
"car_transport": "Car Transport",
|
||||
"car_transport__subtitle": "Traditional automobile transport, convenient prices, and maneuverability.",
|
||||
"water_transport": "Water Transport",
|
||||
"water_transport__subtitle": "CPost specializes in transporting goods by sea.",
|
||||
"railway_transport": "Railway Transport",
|
||||
"railway_transport__subtitle": "Railway transport is one of the main directions of CPost activity. The company transports with necessary tonnage containers and all types of wagons.",
|
||||
"warehouse_yivu_and_guanchjou": "Warehouse Services in Yiwu and Guangzhou",
|
||||
"warehouse_yivu_and_guanchjou__subtitle": "CPost offers the use of warehouse logistics services in China simultaneously with transporting vehicles from China.",
|
||||
"customs_office_service": "Customs Clearance Services",
|
||||
"customs_office_service__subtitle": "CPost values its customers' time and offers customs declaration services for goods. Classification of goods by HS and TN codes.",
|
||||
|
||||
"pricing": {
|
||||
"title": "Pricing",
|
||||
"limit": "Delivery time:",
|
||||
"min_weight": "Minimum Weight",
|
||||
"more": "More Details",
|
||||
|
||||
"pricing1": {
|
||||
"title": "Air Transport",
|
||||
"price": "From $10/KG",
|
||||
"limit": "7-14 days",
|
||||
"min_weight": "1kg"
|
||||
},
|
||||
"pricing2": {
|
||||
"title": "Express Car Transport",
|
||||
"price": "From $8/KG",
|
||||
"limit": "30 days",
|
||||
"min_weight": "1kg"
|
||||
},
|
||||
"pricing3": {
|
||||
"title": "Car Shipping",
|
||||
"price": "Based on agreement",
|
||||
"limit": "20-25 days",
|
||||
"min_weight": "1m3"
|
||||
},
|
||||
"pricing4": {
|
||||
"title": "Railway Transport",
|
||||
"price": "Based on agreement",
|
||||
"limit": "35-40 days",
|
||||
"min_weight": "1m3"
|
||||
}
|
||||
},
|
||||
|
||||
"our_partners": "Our Partners",
|
||||
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
"contacts_subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
|
||||
"address": "Address",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"telegram": "Telegram",
|
||||
"instagram": "Instagram",
|
||||
"real_address": "Tashkent city, Olmazor district, _______ street",
|
||||
|
||||
"faq": {
|
||||
"title": "Frequently Asked Questions",
|
||||
"subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
|
||||
"faq1": {
|
||||
"question": "How can I track my shipment in real-time?",
|
||||
"answer": "Most logistics companies provide online tracking services through their websites or mobile apps. You can enter your tracking number to get real-time updates on the delivery location and status."
|
||||
}
|
||||
},
|
||||
|
||||
"footer_site_info_text": "Reliable and Fast Logistics Service Provider",
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"terms_of_use": "Terms of Use",
|
||||
"sign_up_to_tg": "Sign upfor the latest news, company insights, and CPost updates.",
|
||||
"your_email": "Your email",
|
||||
"pages": "Pages",
|
||||
"info": "Information",
|
||||
"join_tg": "Join our telegram",
|
||||
"all_rights_reserved": "All Rights Reserved",
|
||||
|
||||
"calc_obj": {
|
||||
"title": "Calculator",
|
||||
"subtitle": "Select the delivery type and weight of your order to find out the delivery cost",
|
||||
"colvo_sht": "Quantity (pcs)",
|
||||
"volume": "Volume (m3)",
|
||||
"weight_of_product": "Weight of the cargo...",
|
||||
"calc_value": "Calculate"
|
||||
},
|
||||
"check_order_obj": {
|
||||
"title": "Parcel Tracking",
|
||||
"subtitle": "Enter the barcode of the parcel received from the online store.",
|
||||
"enter_check_code": "Enter the barcode...",
|
||||
"select_delivery_type": "Select the delivery type",
|
||||
"delivery_type_avia": "Air",
|
||||
"delivery_type_car": "Auto"
|
||||
},
|
||||
"price": "Price",
|
||||
|
||||
"__dash": "------------------------------------DASHBOARD------------------------------------------------------",
|
||||
"dashboard": "Dashboard",
|
||||
"parties": "Parties",
|
||||
"create_party": "Create party",
|
||||
"update_party": "Update party",
|
||||
"create_box": "Create box",
|
||||
"create_packet": "Create packet",
|
||||
"update_box": "Update box",
|
||||
"update_package": "Update packet",
|
||||
"update_item": "Update item",
|
||||
"no": "No",
|
||||
"id": "ID",
|
||||
"name": "Name",
|
||||
"party_status": "Party status",
|
||||
"box_status": "Box status",
|
||||
"actions": "Actions",
|
||||
"reset_filter": "Reset filter",
|
||||
"search": "Search",
|
||||
"boxes": "Boxes",
|
||||
"packet": "Packets",
|
||||
"filter": "Filter",
|
||||
"party_name": "Party name",
|
||||
"box_type": "Box type",
|
||||
"box_number": "Box number",
|
||||
"net_weight": "Net weight",
|
||||
"gross_weight": "Brutto",
|
||||
"box_size": "Box size",
|
||||
"box_weight": "Box weight",
|
||||
"volume": "Volume",
|
||||
"netto": "Netto",
|
||||
"brutto": "Brutto",
|
||||
"description": "Description",
|
||||
"add_product": "Add product",
|
||||
"add_box": "Add box",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"cargo_id": "Cargo id",
|
||||
"please_enter_use_id": "Please enter use id",
|
||||
"quantity": "Quantity",
|
||||
"weight": "Weight",
|
||||
"add_more": "Add more",
|
||||
"create": "Create",
|
||||
"cancel": "Cancel",
|
||||
"update": "Update",
|
||||
"clients": "Clients",
|
||||
"client": "Client",
|
||||
"create_client": "Create client",
|
||||
"client_name": "Client name",
|
||||
"fullName": "Fullname",
|
||||
"products": "Products",
|
||||
"product_name": "Product name",
|
||||
"track_id": "Track id",
|
||||
"party": "Party",
|
||||
"staffs": "Staffs",
|
||||
"create_staff": "Create staff",
|
||||
"username": "User name",
|
||||
"role": "Role",
|
||||
"status": "Status",
|
||||
"active": "Active",
|
||||
"not_active": "Not active",
|
||||
"log_out": "Log out",
|
||||
"password": "Password",
|
||||
"create_invoice": "Create invoice",
|
||||
"hasInvoice": "Has invoice",
|
||||
"required": "Required",
|
||||
"total_price": "Total price",
|
||||
"not_found": "Not found",
|
||||
"loading": "Loading",
|
||||
"count_of_boxes": "Count of boxes",
|
||||
"count_of_items": "Count of items",
|
||||
"download_excel": "Download excel",
|
||||
|
||||
"enter_party_name_to_find": "Enter party name to find...",
|
||||
"enter_box_name_to_find": "Enter box name to find...",
|
||||
"enter_client_name_to_find": "Enter client name to find...",
|
||||
"filter_packet_name": "Packet name filter",
|
||||
|
||||
"filter_party_name": "Party name filter",
|
||||
"filter_box_name": "Box name filter",
|
||||
"filter_track_id": "Track id filter",
|
||||
"filter_client_name": "Client name filter",
|
||||
"filter_item_name": "Product name filter",
|
||||
"filter_by_box_status": "Box Status filter",
|
||||
"filter_by_party_status": "Party Status filter",
|
||||
|
||||
"COLLECTING": "Collecting",
|
||||
"ON_THE_WAY": "On the way",
|
||||
"ARRIVED": "Arrived",
|
||||
"READY_TO_INVOICE": "Ready to invoice",
|
||||
"READY": "Ready",
|
||||
"IN_CUSTOMS": "At customs",
|
||||
"IN_WAREHOUSE": "Reaching out to customers",
|
||||
"DELIVERED": "Delivered",
|
||||
|
||||
"add_photo_to_item": "Add photo to item",
|
||||
"photo": "photo",
|
||||
"summa": "summa",
|
||||
"avia_cargo_id": "Avia cargo id",
|
||||
"auto_cargo_id": "Auto cargo id",
|
||||
"update_client": "Update client",
|
||||
"pinfl": "Pinfl",
|
||||
"passportSeries": "Passport series",
|
||||
"search_available_in_trekid": "Here you can only search for a product by TrekID",
|
||||
"party_total_brutto": "Party's total brutto",
|
||||
"date_of_birth": "Date of birth",
|
||||
"are_you_sure_delete_client_id": "Are you sure to delete client {id}?",
|
||||
"download_all_items": "Download all items",
|
||||
"download_all_clients": "Download all clients",
|
||||
"are_you_sure_delete_party": "Are you sure to delete party {name}?",
|
||||
"passport": "Passport",
|
||||
"weight_of_items": "Products weight",
|
||||
"update_packet": "Edit Paket",
|
||||
"party_weight": "Batch weight"
|
||||
}
|
||||
225
messages/ru.json
Normal file
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"main_page": "Главная страница",
|
||||
"home": "Главная",
|
||||
"services": "Услуги",
|
||||
"news": "Новости",
|
||||
"about_us": "О нас",
|
||||
"tariffs": "Тарифы",
|
||||
"contacts": "Контакты",
|
||||
"check_order": "Проверить заказ",
|
||||
"hero_title": "Доставка товаров из Китая в Узбекистан",
|
||||
"hero_subtitle": "Хотите узнать, как доставить свои покупки из Китая в Узбекистан?",
|
||||
"good_quality": "Высокое качество",
|
||||
"fast": "Быстро",
|
||||
"online_watching": "Онлайн отслеживание",
|
||||
"safe_warehouse": "Безопасный склад",
|
||||
"we_proud_to_serve_global": "Мы гордимся предоставлением лучших решений в области грузоперевозок и транспортировки по всему миру.",
|
||||
"our_services": "Наши услуги",
|
||||
|
||||
"air_transport": "Авиаперевозки",
|
||||
"air_transport__subtitle": "CPost предлагает авиаперевозки для широкого спектра крупногабаритных и тяжелых товаров.",
|
||||
"car_transport": "Автоперевозки",
|
||||
"car_transport__subtitle": "Традиционные автоперевозки, удобные цены и маневренность.",
|
||||
"water_transport": "Морские перевозки",
|
||||
"water_transport__subtitle": "CPost специализируется на транспортировке товаров морским путем.",
|
||||
"railway_transport": "Железнодорожные перевозки",
|
||||
"railway_transport__subtitle": "Железнодорожные перевозки - одно из основных направлений деятельности CPost. Компания транспортирует контейнеры с необходимой грузоподъемностью и все виды вагонов.",
|
||||
"warehouse_yivu_and_guanchjou": "Складские услуги в Йиву и Гуанчжоу",
|
||||
"warehouse_yivu_and_guanchjou__subtitle": "CPost предлагает использование услуг складской логистики в Китае параллельно с транспортировкой транспортных средств из Китая.",
|
||||
"customs_office_service": "Таможенные услуги",
|
||||
"customs_office_service__subtitle": "CPost ценит время своих клиентов и предлагает услуги по таможенному оформлению товаров. Классификация товаров по кодам HS и TN.",
|
||||
|
||||
"pricing": {
|
||||
"title": "Ценообразование",
|
||||
"limit": "Срок доставки:",
|
||||
"min_weight": "Минимальный вес",
|
||||
"more": "Подробнее",
|
||||
|
||||
"pricing1": {
|
||||
"title": "Авиаперевозки",
|
||||
"price": "От $10/кг",
|
||||
"limit": "7-14 дней",
|
||||
"min_weight": "1кг"
|
||||
},
|
||||
"pricing2": {
|
||||
"title": "Экспресс автоперевозки",
|
||||
"price": "От $8/кг",
|
||||
"limit": "30 дней",
|
||||
"min_weight": "1кг"
|
||||
},
|
||||
"pricing3": {
|
||||
"title": "Автомобильные перевозки",
|
||||
"price": "По соглашению",
|
||||
"limit": "20-25 дней",
|
||||
"min_weight": "1м3"
|
||||
},
|
||||
"pricing4": {
|
||||
"title": "Железнодорожные перевозки",
|
||||
"price": "По соглашению",
|
||||
"limit": "35-40 дней",
|
||||
"min_weight": "1м3"
|
||||
}
|
||||
},
|
||||
|
||||
"our_partners": "Наши партнеры",
|
||||
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
"contacts_subtitle": "Creative mouse здесь для вас, чтобы убедиться, что вы можете построить более устойчивый портфель. Мы создаем индивидуальные предложения в альтернативной области.",
|
||||
"address": "Адрес",
|
||||
"email": "Электронная почта",
|
||||
"phone": "Телефон",
|
||||
"telegram": "Telegram",
|
||||
"instagram": "Instagram",
|
||||
"real_address": "Город Ташкент, район Ольмазор, улица _______",
|
||||
|
||||
"faq": {
|
||||
"title": "Часто задаваемые вопросы",
|
||||
"subtitle": "Creative mouse здесь для вас, чтобы убедиться, что вы можете построить более устойчивый портфель. Мы создаем индивидуальные предложения в альтернативной области.",
|
||||
"faq1": {
|
||||
"question": "Как я могу отслеживать мою отправку в реальном времени?",
|
||||
"answer": "Большинство логистических компаний предоставляют услуги онлайн-отслеживания через свои веб-сайты или мобильные приложения. Вы можете ввести свой номер отслеживания, чтобы получить обновления о местоположении и статусе доставки в реальном времени."
|
||||
}
|
||||
},
|
||||
|
||||
"footer_site_info_text": "Надежный и быстрый поставщик логистических услуг",
|
||||
"privacy_policy": "Политика конфиденциальности",
|
||||
"terms_of_use": "Условия использования",
|
||||
"sign_up_to_tg": "Подпишитесь на последние новости, корпоративные исследования и обновления CPost.",
|
||||
"your_email": "Ваш адрес электронной почты",
|
||||
"pages": "Страницы",
|
||||
"info": "Информация",
|
||||
"join_tg": "Присоединяйтесь к нашему телеграмм-каналу",
|
||||
"all_rights_reserved": "Все права защищены",
|
||||
|
||||
"calc_obj": {
|
||||
"title": "Калькулятор",
|
||||
"subtitle": "Выберите тип доставки и вес вашего заказа, чтобы узнать стоимость доставки",
|
||||
"colvo_sht": "Количество (шт)",
|
||||
"volume": "Объем (м3)",
|
||||
"weight_of_product": "Вес груза...",
|
||||
"calc_value": "Рассчитать"
|
||||
},
|
||||
"check_order_obj": {
|
||||
"title": "Отслеживание посылки",
|
||||
"subtitle": "Введите штрих-код посылки, полученной из интернет-магазина.",
|
||||
"enter_check_code": "Введите штрих-код...",
|
||||
"select_delivery_type": "Выберите тип доставки",
|
||||
"delivery_type_avia": "Авиа",
|
||||
"delivery_type_car": "Авто"
|
||||
},
|
||||
"price": "Цена",
|
||||
|
||||
"__dash": "------------------------------------ПАНЕЛЬ УПРАВЛЕНИЯ------------------------------------------------------",
|
||||
"dashboard": "Панель управления",
|
||||
"parties": "Партии",
|
||||
"create_party": "Создать партию",
|
||||
"update_party": "Обновить партию",
|
||||
"create_box": "Создать коробку",
|
||||
"create_packet": "Создать пакет",
|
||||
"update_box": "Обновить коробку",
|
||||
"update_package": "Обновление пакета",
|
||||
"update_item": "Обновить товар",
|
||||
"no": "Нет",
|
||||
"id": "ID",
|
||||
"name": "Название",
|
||||
"party_status": "Статус партии",
|
||||
"box_status": "Статус ящика",
|
||||
"actions": "Действия",
|
||||
"reset_filter": "Сбросить фильтр",
|
||||
"search": "Поиск",
|
||||
"boxes": "Коробки",
|
||||
"packet": "Пакеты",
|
||||
"filter": "Фильтр",
|
||||
"party_name": "Название партии",
|
||||
"box_type": "Тип коробки",
|
||||
"box_number": "Номер коробки",
|
||||
"net_weight": "Вес нетто",
|
||||
"gross_weight": "Вес брутто",
|
||||
"box_size": "Размер коробки",
|
||||
"box_weight": "Вес коробки",
|
||||
"volume": "Объем",
|
||||
"netto": "Нетто",
|
||||
"brutto": "Брутто",
|
||||
"description": "Описание",
|
||||
"add_product": "Добавить товар",
|
||||
"add_box": "Добавить коробку",
|
||||
"edit": "Редактировать",
|
||||
"delete": "Удалить",
|
||||
"cargo_id": "ID груза",
|
||||
"please_enter_use_id": "Введите ID пользователя",
|
||||
"quantity": "Количество",
|
||||
"weight": "Вес",
|
||||
"add_more": "Добавить еще",
|
||||
"create": "Создать",
|
||||
"cancel": "Отмена",
|
||||
"update": "Обновить",
|
||||
"clients": "Клиенты",
|
||||
"client": "Клиент",
|
||||
"create_client": "Создать клиента",
|
||||
"client_name": "Имя клиента",
|
||||
"fullName": "Полное имя",
|
||||
"products": "Товары",
|
||||
"product_name": "Название товара",
|
||||
"track_id": "ID трека",
|
||||
"party": "Партия",
|
||||
"staffs": "Сотрудники",
|
||||
"create_staff": "Создать сотрудника",
|
||||
"username": "Имя пользователя",
|
||||
"role": "Роль",
|
||||
"status": "Статус",
|
||||
"active": "Активный",
|
||||
"not_active": "Неактивный",
|
||||
"log_out": "Выйти",
|
||||
"password": "Пароль",
|
||||
"create_invoice": "Создать счет",
|
||||
"hasInvoice": "Есть счет",
|
||||
"required": "Обязательно",
|
||||
"total_price": "Общая стоимость",
|
||||
"not_found": "Не найдено",
|
||||
"loading": "Загрузка",
|
||||
"count_of_boxes": "Количество коробок",
|
||||
"count_of_items": "Количество товаров",
|
||||
"download_excel": "Скачать Excel",
|
||||
|
||||
"enter_party_name_to_find": "Введите имя партии для поиска...",
|
||||
"enter_box_name_to_find": "Введите имя коробки для поиска...",
|
||||
"enter_client_name_to_find": "Введите имя клиента для поиска...",
|
||||
|
||||
"filter_packet_name": "Фильтр имени пакета",
|
||||
|
||||
"filter_party_name": "Фильтр по имени партии",
|
||||
"filter_box_name": "Фильтр по имени коробки",
|
||||
"filter_track_id": "Фильтр по идентификатору отслеживания",
|
||||
"filter_client_name": "Фильтр по имени клиента",
|
||||
"filter_item_name": "Фильтр по имени продукта",
|
||||
"filter_by_box_status": "Фильтр по статусу коробки",
|
||||
"filter_by_party_status": "Фильтр по статусу партии",
|
||||
|
||||
"COLLECTING": "Сбор",
|
||||
"ON_THE_WAY": "В пути",
|
||||
"ARRIVED": "Прибыл",
|
||||
"READY_TO_INVOICE": "Готов к выставлению счета",
|
||||
"READY": "Готов",
|
||||
"IN_CUSTOMS": "На таможне",
|
||||
"IN_WAREHOUSE": "Это приближается",
|
||||
"DELIVERED": "Доставленный",
|
||||
|
||||
"add_photo_to_item": "Добавить фотографию к элементу",
|
||||
"photo": "Фото",
|
||||
"summa": "Сумма",
|
||||
"avia_cargo_id": "Идентификатор авиагруза",
|
||||
"auto_cargo_id": "Идентификатор авто груза",
|
||||
"update_client": "Обновить Клиента",
|
||||
"pinfl": "Пинфл",
|
||||
"passportSeries": "Серия паспорта",
|
||||
"search_available_in_trekid": "Здесь вы можете искать товар только по TrekID.",
|
||||
"party_total_brutto": "Полное брутто партии",
|
||||
"date_of_birth": "Дата рождения",
|
||||
"are_you_sure_delete_client_id": "Вы уверены, что хотите удалить клиента {id}?",
|
||||
"download_all_items": "Скачать все продуктов",
|
||||
"download_all_clients": "Скачать всех клиентов",
|
||||
"are_you_sure_delete_party": "Вы уверены, что хотите удалить партию груза {name}?",
|
||||
"passport": "Паспорт",
|
||||
"weight_of_items": "Вес продукта",
|
||||
"update_packet": "Редактировать пакет",
|
||||
"party_weight": "Вес партии"
|
||||
}
|
||||
224
messages/uz.json
Normal file
@@ -0,0 +1,224 @@
|
||||
{
|
||||
"main_page": "Bosh Sahifa",
|
||||
"home": "Bosh sahifa",
|
||||
"services": "Xizmatlar",
|
||||
"news": "Yangiliklar",
|
||||
"about_us": "Biz Haqimizda",
|
||||
"tariffs": "Tariflar",
|
||||
"contacts": "Kontaktlar",
|
||||
"check_order": "Buyurtmani Tekshirish",
|
||||
"hero_title": "Xitoydan O'zbekistonga Mahsulot Yetkazib Berish",
|
||||
"hero_subtitle": "Xitoydan sotib olingan mahsulotlaringizni O'zbekistonga qanday yetkazib berishni bilsangizmi?",
|
||||
"good_quality": "Yuqori sifat",
|
||||
"fast": "Tezkor",
|
||||
"online_watching": "Onlayn Kuzatish",
|
||||
"safe_warehouse": "Xavfsiz Ombor",
|
||||
"we_proud_to_serve_global": "Biz dunyo bo'yligicha eng yaxshi yuk va transport yechimlari bilan iftixorlanamiz.",
|
||||
"our_services": "Bizning Xizmatlarimiz",
|
||||
|
||||
"air_transport": "Havo Yo'li",
|
||||
"air_transport__subtitle": "CPost katta hajmli va og'ir mahsulotlarni yo'lga qo'yish uchun havo yo'li yetkazib beradi.",
|
||||
"car_transport": "Avtotrport",
|
||||
"car_transport__subtitle": "Odatiy avtotransport, qulay narxlar va manevr.",
|
||||
"water_transport": "Su Yo'li",
|
||||
"water_transport__subtitle": "CPost maxsusligi su yo'li bilan mahsulotlarni yo'lga qo'yadi.",
|
||||
"railway_transport": "Temir Yo'li Yo'li",
|
||||
"railway_transport__subtitle": "Temir yo'li transporti CPost faoliyatining asosiy yo'nalishlaridan biri. Kompaniya zarur tovar konteynerlari va barcha turlaridagi vagonlar bilan transport qiladi.",
|
||||
"warehouse_yivu_and_guanchjou": "Yiwu va Guanchjoudagi Ombor Xizmatlari",
|
||||
"warehouse_yivu_and_guanchjou__subtitle": "CPost Xitoydan transport vositalarini yetkazib berish bilan bir vaqtning o'zida Xitoyda ombor loyixasi xizmatlaridan foydalanishni taklif qiladi.",
|
||||
"customs_office_service": "Ma'lumotlar Markazi Xizmatlari",
|
||||
"customs_office_service__subtitle": "CPost mijozlari vaqtini qadrlaydi va tovarlar uchun ma'lumotnoma ma'lumotlari taqdim qiladi. Mahsulotlarni HS va TN kodalari bo'yicha tartiblash.",
|
||||
|
||||
"pricing": {
|
||||
"title": "Narxlar",
|
||||
"limit": "Yetkazib berish vaqti:",
|
||||
"min_weight": "Minimal Og'irlik",
|
||||
"more": "Batafsil",
|
||||
|
||||
"pricing1": {
|
||||
"title": "Havo Yo'li Yo'li",
|
||||
"price": "$10/KG dan",
|
||||
"limit": "7-14 kun",
|
||||
"min_weight": "1kg"
|
||||
},
|
||||
"pricing2": {
|
||||
"title": "Express Avtotransport",
|
||||
"price": "$8/KG dan",
|
||||
"limit": "30 kun",
|
||||
"min_weight": "1kg"
|
||||
},
|
||||
"pricing3": {
|
||||
"title": "Avtomobil Yo'li Yo'li",
|
||||
"price": "Kelishuv asosida",
|
||||
"limit": "20-25 kun",
|
||||
"min_weight": "1m3"
|
||||
},
|
||||
"pricing4": {
|
||||
"title": "Temir Yo'li Yo'li",
|
||||
"price": "Kelishuv asosida",
|
||||
"limit": "35-40 kun",
|
||||
"min_weight": "1m3"
|
||||
}
|
||||
},
|
||||
|
||||
"our_partners": "Bizning Hamkorlarimiz",
|
||||
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
"contacts_subtitle": "Kreativ quyuq siz uchun bu joyda, sizga zaminli portfelingizni qurishingiz uchun. Biz alternativ tomonlar bo'yicha maxsus takliflar tuzamiz.",
|
||||
"address": "Manzil",
|
||||
"email": "Elektron pochta",
|
||||
"phone": "Telefon",
|
||||
"telegram": "Telegram",
|
||||
"instagram": "Instagram",
|
||||
"real_address": "Toshkent shahri, Olmazor tumani, _______ ko'chasi",
|
||||
|
||||
"faq": {
|
||||
"title": "Tez-tez So'raladigan Savollar",
|
||||
"subtitle": "Kreativ quyuq siz uchun bu joyda, sizga zaminli portfelingizni qurishingiz uchun. Biz alternativ tomonlar bo'yicha maxsus takliflar tuzamiz.",
|
||||
"faq1": {
|
||||
"question": "Men qanday qilib yukni real vaqtda kuzatib boring?",
|
||||
"answer": "Ko'plab logistika kompaniyalari o'z saytlari yoki mobil ilovalari orqali onlayn kuzatish xizmatlarini taqdim qiladilar. Siz o'zingizning kuzatish raqamingizni kiriting, yetkazib berish joyini va holatini haqiqiy vaqt ichida yangilashlarni olish uchun."
|
||||
}
|
||||
},
|
||||
|
||||
"footer_site_info_text": "Ishonchli va Tezkor Logistika Xizmati Taqdimoti",
|
||||
"privacy_policy": "Maxfiylik Siyosati",
|
||||
"terms_of_use": "Foydalanish shartlari",
|
||||
"sign_up_to_tg": "Eng so'nggi yangiliklar, kompaniya ma'lumotlari va CPost yangilanishlari uchun ro'yxatdan o'ting.",
|
||||
"your_email": "Sizning elektron pochtangiz",
|
||||
"pages": "Sahifalar",
|
||||
"info": "Ma'lumot",
|
||||
"join_tg": "Bizning telegramm jamoamizga qo'shiling",
|
||||
"all_rights_reserved": "Barcha huquqlar himoyalangan",
|
||||
|
||||
"calc_obj": {
|
||||
"title": "Kalkulyator",
|
||||
"subtitle": "Buyurtma turi va og'irlikni tanlang, yetkazib berish narxini bilib olish uchun",
|
||||
"colvo_sht": "Miqdori (donalar)",
|
||||
"volume": "Hajmi (m3)",
|
||||
"weight_of_product": "Yuk og'irliği...",
|
||||
"calc_value": "Hisoblash"
|
||||
},
|
||||
"check_order_obj": {
|
||||
"title": "Parsel Kuzatish",
|
||||
"subtitle": "Onlayn do'kondan olingan parselning shtrix-kodini kiriting.",
|
||||
"enter_check_code": "Shtrix-kodni kiriting...",
|
||||
"select_delivery_type": "Yetkazib berish turlarini tanlang",
|
||||
"delivery_type_avia": "Havo",
|
||||
"delivery_type_car": "Avtomobil"
|
||||
},
|
||||
"price": "Narx",
|
||||
|
||||
"__dash": "------------------------------------BOSHQARUV PANELI------------------------------------------------------",
|
||||
"dashboard": "Boshqaruv paneli",
|
||||
"parties": "Partiyalar",
|
||||
"create_party": "Partiya yaratish",
|
||||
"update_party": "Partiyani yangilash",
|
||||
"create_box": "Qutni yaratish",
|
||||
"create_packet": "Paket yaratish",
|
||||
"update_box": "Qutni yangilash",
|
||||
"update_package": "Paketni yangilash",
|
||||
"update_item": "Mahsulotni yangilash",
|
||||
"no": "Yo'q",
|
||||
"id": "ID",
|
||||
"name": "Ism",
|
||||
"party_status": "Partiya holati",
|
||||
"actions": "Harakatlar",
|
||||
"reset_filter": "Filtrni qayta o'rnatish",
|
||||
"search": "Qidiruv",
|
||||
"boxes": "Qutilar",
|
||||
"packet": "Paketlar",
|
||||
"filter": "Filtr",
|
||||
"party_name": "Partiya nomi",
|
||||
"box_type": "Qutni turi",
|
||||
"box_number": "Qutni raqami",
|
||||
"net_weight": "Netto og'irlik",
|
||||
"gross_weight": "Brutto vazni",
|
||||
"box_size": "Qutni o'lchami",
|
||||
"box_weight": "Qut og'irliği",
|
||||
"volume": "Hajm",
|
||||
"netto": "Netto",
|
||||
"brutto": "Brutto",
|
||||
"description": "Ta'rif",
|
||||
"add_product": "Mahsulot qo'shish",
|
||||
"add_box": "Qut qo'shish",
|
||||
"edit": "Tahrirlash",
|
||||
"delete": "O'chirish",
|
||||
"cargo_id": "Kargo ID",
|
||||
"please_enter_use_id": "Iltimos, foydalanuvchi ID sini kiriting",
|
||||
"quantity": "Miqdor",
|
||||
"weight": "Og'irlik",
|
||||
"add_more": "Qo'shish",
|
||||
"create": "Yaratish",
|
||||
"cancel": "Bekor qilish",
|
||||
"update": "Yangilash",
|
||||
"clients": "Mijozlar",
|
||||
"client": "Mijoz",
|
||||
"create_client": "Mijoz yaratish",
|
||||
"client_name": "Mijoz nomi",
|
||||
"fullName": "To'liq ism",
|
||||
"products": "Mahsulotlar",
|
||||
"product_name": "Mahsulot nomi",
|
||||
"track_id": "Trassirovka ID",
|
||||
"party": "Partiya",
|
||||
"staffs": "Xodimlar",
|
||||
"create_staff": "Xodim yaratish",
|
||||
"username": "Foydalanuvchi nomi",
|
||||
"role": "Rol",
|
||||
"status": "Holat",
|
||||
"active": "Faol",
|
||||
"not_active": "Faol emas",
|
||||
"log_out": "Chiqish",
|
||||
"password": "Parol",
|
||||
"create_invoice": "Sinfakt yaratish",
|
||||
"hasInvoice": "Invoice mavjud",
|
||||
"required": "Talab qilinadi",
|
||||
"total_price": "Umumiy narxi",
|
||||
"not_found": "Topilmadi",
|
||||
"loading": "Yuklanmoqda",
|
||||
"count_of_boxes": "Qutilar soni",
|
||||
"count_of_items": "Mahsulotlar soni",
|
||||
"download_excel": "Excelni yuklash",
|
||||
"weight_of_items": "Mahsulotlar og'irligi",
|
||||
|
||||
"enter_party_name_to_find": "Qidirish uchun partiya nomini kiriting...",
|
||||
"enter_box_name_to_find": "Qidirish uchun qutini nomini kiriting...",
|
||||
"enter_client_name_to_find": "Qidirish uchun mijoz nomini kiriting...",
|
||||
|
||||
"filter_packet_name": "Paket nomi filtri",
|
||||
|
||||
"filter_party_name": "Partiya nomi filtri",
|
||||
"filter_box_name": "Quti nomi filtri",
|
||||
"filter_track_id": "Kuzatish ID filtri",
|
||||
"filter_client_name": "Mijoz nomi filtri",
|
||||
"filter_item_name": "Mahsulot nomi filtri",
|
||||
"filter_by_box_status": "Qut statusi filtri",
|
||||
"filter_by_party_status": "Partiya holati filtri",
|
||||
|
||||
"COLLECTING": "To'plash",
|
||||
"ON_THE_WAY": "Yo'lda",
|
||||
"ARRIVED": "Yetib keldi",
|
||||
"READY_TO_INVOICE": "Hisob-fakturaga tayyor",
|
||||
"READY": "Tayyor",
|
||||
"IN_CUSTOMS": "Bojxonada",
|
||||
"IN_WAREHOUSE": "Yetkizilyabdi",
|
||||
"DELIVERED": "Yetkazildi",
|
||||
|
||||
"add_photo_to_item": "Mahsulotga rasm qo'shish",
|
||||
"photo": "Rasm",
|
||||
"summa": "Summa",
|
||||
"avia_cargo_id": "Aviatsiya yuk identifikatori",
|
||||
"auto_cargo_id": "Avto yuk identifikatori",
|
||||
"update_client": "Mijozni tahrirlash",
|
||||
"pinfl": "Pinfl",
|
||||
"passportSeries": "Passport seriya",
|
||||
"search_available_in_trekid": "Bu yerda faqat TrekID orqali mahsulotni qidirishingiz mumkin",
|
||||
"party_total_brutto": "Partiyaning umumiy bruttosi",
|
||||
"date_of_birth": "Tug'ilgan kuni",
|
||||
"are_you_sure_delete_client_id": "Rostdan ham {id} id lik klientni o'chirmoqchimisiz?",
|
||||
"download_all_items": "Barcha mahsulotlarni yuklab olish",
|
||||
"download_all_clients": "Barcha mijozlarni yuklab olish",
|
||||
"are_you_sure_delete_party": "{name} yuk partiyasini o'chirishga ishonchingiz komilmi?",
|
||||
"passport": "Pasport",
|
||||
"update_packet": "Paketni Tahrirlash",
|
||||
"party_weight": "Partiya og'irligi"
|
||||
}
|
||||
25
next.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const withNextIntl = require('next-intl/plugin')('./i18n.ts');
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: '**',
|
||||
},
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: '**',
|
||||
},
|
||||
],
|
||||
},
|
||||
output: 'standalone',
|
||||
|
||||
reactStrictMode: false,
|
||||
typescript: {
|
||||
ignoreBuildErrors: false,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = withNextIntl(nextConfig);
|
||||
8579
package-lock.json
generated
Normal file
75
package.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "c-post",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.uz next dev --port=3080",
|
||||
"build": "next build",
|
||||
"build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.uz next build",
|
||||
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"test": "vitest",
|
||||
"check-types": "tsc --noemit",
|
||||
"format-code": "prettier --write .",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.15.3",
|
||||
"@mui/material": "^5.15.3",
|
||||
"@mui/material-nextjs": "^5.15.3",
|
||||
"@tanstack/react-query": "^4.35.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.17.9",
|
||||
"aos": "^2.3.4",
|
||||
"axios": "^1.6.5",
|
||||
"cookies-next": "^4.1.0",
|
||||
"i18next": "^23.7.16",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"next": "14.0.4",
|
||||
"next-i18next": "^15.2.0",
|
||||
"next-intl": "^3.4.2",
|
||||
"nextjs-progressbar": "^0.0.16",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-hook-form": "^7.49.3",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-select": "^5.8.0",
|
||||
"simplebar-react": "^3.2.4",
|
||||
"swiper": "^11.0.5",
|
||||
"use-dehydrated-state": "^0.1.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/react-query-devtools": "^4.35.3",
|
||||
"@types/aos": "^3.0.7",
|
||||
"@types/i18next": "^13.0.0",
|
||||
"@types/lodash.clonedeep": "^4.5.9",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/lodash.get": "^4.4.9",
|
||||
"@types/lodash.omit": "^4.5.9",
|
||||
"@types/node": "^20",
|
||||
"@types/parse-json": "^4.0.2",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react-i18next": "^8.1.0",
|
||||
"@types/react-select": "^5.0.1",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.4",
|
||||
"husky": "^8.0.0",
|
||||
"jsdom": "^23.2.0",
|
||||
"lint-staged": "^15.2.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5",
|
||||
"vitest": "^1.1.3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 765 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/logo.jpeg
Normal file
|
After Width: | Height: | Size: 28 KiB |
8
public/robots.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# sitecode:cpost-express
|
||||
Host: https://cpost-express.uz
|
||||
Sitemap: https://cpost-express.uz/sitemap.xml
|
||||
User-agent: *
|
||||
Disallow: */dashboard/*
|
||||
Disallow: */login/*
|
||||
Disallow: */news/*
|
||||
Allow: /
|
||||
11
public/site.webmanifest
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "CPost",
|
||||
"short_name": "CPost",
|
||||
"icons": [
|
||||
{ "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
11
public/sitemap.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap>
|
||||
<loc>https://cpost-express.uz/en</loc>
|
||||
</sitemap>
|
||||
<sitemap>
|
||||
<loc>https://cpost-express.uz/ru</loc>
|
||||
</sitemap>
|
||||
<sitemap>
|
||||
<loc>https://cpost-express.uz/uz</loc>
|
||||
</sitemap>
|
||||
</sitemapindex>
|
||||
BIN
public/static/icons/cn-flag.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/static/icons/en-flag.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
public/static/icons/ru-flag.webp
Normal file
|
After Width: | Height: | Size: 164 B |
BIN
public/static/icons/uz-flag.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/static/images/airplane.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
14
public/static/images/airplane.svg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/static/images/brand1.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/static/images/brand2.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/static/images/brand3.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
public/static/images/brand4.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/static/images/demo-user.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
public/static/images/faq-bg.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
public/static/images/hero.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
public/static/images/icon1.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/static/images/icon2.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/static/images/icon3.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/static/images/icon4.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
14
public/static/images/long-truck.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
14
public/static/images/metro.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/static/images/news-bg.png
Normal file
|
After Width: | Height: | Size: 688 KiB |
BIN
public/static/images/news1.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
public/static/images/news2.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
public/static/images/news3.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
public/static/images/news4.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/static/images/pricing-bg.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
public/static/images/service1.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/static/images/service2.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
public/static/images/service3.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/static/images/services-bg.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
14
public/static/images/truck.svg
Normal file
|
After Width: | Height: | Size: 12 KiB |
256
src/app/[locale]/(views)/profile-edit/page.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Box, TextField, Typography, Grid, Button, Card, CardMedia, CardContent } from '@mui/material';
|
||||
import Layout from '@/components/layout/landing-layout';
|
||||
import Container from '@/components/common/Container';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
|
||||
export default function UserEditForm() {
|
||||
const { back, replace } = useMyNavigation();
|
||||
const [userData, setUserData] = useState({
|
||||
fullName: 'Helen Zhou',
|
||||
birthDate: '1990-11-11',
|
||||
phone: '+998 99 365 48 11',
|
||||
serialNumber: 'AB342534',
|
||||
pinfl: '12548965451254',
|
||||
address: 'Toshkent sh. Uchtepa 12',
|
||||
aviaID: 'AT010001',
|
||||
cargoID: 'T010001',
|
||||
branch: 'Toshkent sh',
|
||||
});
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
const [passportFrontImage, setPassportFrontImage] = useState<string | null>(
|
||||
'https://a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2018/09/1200/675/Ireland-Plastic-Passports-1.jpg?ve=1&tl=1'
|
||||
);
|
||||
const [passportBackImage, setPassportBackImage] = useState<string | null>(
|
||||
'https://nebula.wsimg.com/08bfbadc8356b6a0ba6fd1a05dbc4987?AccessKeyId=80E5C6BFDBBCCE7AC676&disposition=0&alloworigin=1'
|
||||
);
|
||||
const [userImage, setUserImage] = useState<string | null>(
|
||||
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?fm=jpg&q=60&w=3000&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8ZmVtYWxlJTIwcHJvZmlsZXxlbnwwfHwwfHx8MA%3D%3D'
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
replace('/');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setUserData({ ...userData, [e.target.name]: e.target.value });
|
||||
};
|
||||
|
||||
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>, key?: string) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
if (e.target) {
|
||||
if (key === 'front') setPassportFrontImage(e.target.result as string);
|
||||
else if (key === 'user') setUserImage(e.target.result as string);
|
||||
else setPassportBackImage(e.target.result as string);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Container>
|
||||
<Box sx={{ p: { xs: 2, sm: 4 }, mx: 'auto' }}>
|
||||
<Typography variant='h6' fontWeight='bold' mb={2}>
|
||||
{"Ma'lumotlarni tahrirlash"}
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="To'liq ism" name='fullName' value={userData.fullName} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
type='date'
|
||||
label="Tug'ilgan kun"
|
||||
name='birthDate'
|
||||
value={userData.birthDate}
|
||||
onChange={handleChange}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='Telefon raqam' name='phone' value={userData.phone} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label='Seriya raqam'
|
||||
name='serialNumber'
|
||||
value={userData.serialNumber}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='PINFL' name='pinfl' value={userData.pinfl} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='Qulay filial' name='branch' value={userData.branch} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='Adres' name='address' value={userData.address} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='Avia ID' name='aviaID' value={userData.aviaID} onChange={handleChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label='Kargo ID' name='cargoID' value={userData.cargoID} onChange={handleChange} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Box mt={3} sx={{ display: 'flex', gap: '50px' }}>
|
||||
<Box>
|
||||
<Typography variant='body1' fontWeight='bold' mb={1}>
|
||||
Rasmingiz
|
||||
</Typography>
|
||||
|
||||
<input
|
||||
type='file'
|
||||
accept='image/*'
|
||||
onChange={e => handleImageUpload(e, 'user')}
|
||||
style={{ display: 'none' }}
|
||||
id='user-upload'
|
||||
/>
|
||||
<label htmlFor='user-upload'>
|
||||
<Card
|
||||
sx={{
|
||||
width: '80px',
|
||||
height: '80px',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
border: '1px dashed gray',
|
||||
display: 'inline-block',
|
||||
}}
|
||||
>
|
||||
{userImage ? (
|
||||
<CardMedia
|
||||
component='img'
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
image={userImage}
|
||||
alt='Passport Image'
|
||||
/>
|
||||
) : (
|
||||
<CardContent sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography variant='body2' color='gray'>
|
||||
Yuklash uchun bosing
|
||||
</Typography>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</label>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Typography variant='body1' fontWeight='bold' mb={1}>
|
||||
Passport rasmi
|
||||
</Typography>
|
||||
<input
|
||||
type='file'
|
||||
accept='image/*'
|
||||
onChange={e => handleImageUpload(e, 'front')}
|
||||
style={{ display: 'none' }}
|
||||
id='passport-front-upload'
|
||||
/>
|
||||
<input
|
||||
type='file'
|
||||
accept='image/*'
|
||||
onChange={handleImageUpload}
|
||||
style={{ display: 'none' }}
|
||||
id='passport-back-upload'
|
||||
/>
|
||||
<Box sx={{ display: 'flex', gap: '15px' }}>
|
||||
<label htmlFor='passport-front-upload'>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: 250,
|
||||
cursor: 'pointer',
|
||||
border: '1px dashed gray',
|
||||
display: 'inline-block',
|
||||
}}
|
||||
>
|
||||
{passportFrontImage ? (
|
||||
<CardMedia
|
||||
component='img'
|
||||
height='140'
|
||||
sx={{
|
||||
maxWidth: '200px',
|
||||
maxHeight: '200px',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
image={passportFrontImage}
|
||||
alt='Passport Image'
|
||||
/>
|
||||
) : (
|
||||
<CardContent sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography variant='body2' color='gray'>
|
||||
Yuklash uchun bosing
|
||||
</Typography>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</label>
|
||||
{(!!passportFrontImage || !!passportBackImage) && (
|
||||
<label htmlFor='passport-back-upload'>
|
||||
<Card
|
||||
sx={{
|
||||
maxWidth: 250,
|
||||
cursor: 'pointer',
|
||||
border: '1px dashed gray',
|
||||
display: 'inline-block',
|
||||
}}
|
||||
>
|
||||
{!!passportBackImage ? (
|
||||
<CardMedia
|
||||
component='img'
|
||||
height='140'
|
||||
sx={{
|
||||
maxWidth: '200px',
|
||||
maxHeight: '200px',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'center',
|
||||
}}
|
||||
image={passportBackImage}
|
||||
alt='Passport Image'
|
||||
/>
|
||||
) : (
|
||||
<CardContent sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography variant='body2' color='gray'>
|
||||
Yuklash uchun bosing
|
||||
</Typography>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</label>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box mt={3} display='flex' justifyContent='flex-end' gap={2}>
|
||||
<Button onClick={back} variant='outlined' color='primary'>
|
||||
Bekor qilish
|
||||
</Button>
|
||||
<Button variant='contained' color='primary'>
|
||||
Saqlash
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box, Typography, Grid, Button } from '@mui/material';
|
||||
import { IconCopy } from '@/components/common/Icons';
|
||||
|
||||
export default function Page() {
|
||||
const data = [
|
||||
{
|
||||
title: '🚚 Avto Kargo',
|
||||
recipient: '777( TT-001)',
|
||||
phone: '15532640276',
|
||||
address: '主洗车旁1号门777( TT-001)',
|
||||
},
|
||||
{
|
||||
title: '✈️ Avia Kargo',
|
||||
recipient: '乌兹777( TT-001)',
|
||||
phone: '18922155990',
|
||||
address: '楼档口 777( TT-001) AVTO',
|
||||
},
|
||||
];
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text).catch(err => {
|
||||
console.error('Nusxa olishda xatolik yuz berdi:', err);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={2}>
|
||||
<Box sx={{display: 'flex', flexDirection: 'column', gap: '35px'}}>
|
||||
{data.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Typography variant={'h6'} marginBottom={"10px"}>{item.title}</Typography>
|
||||
<Box sx={{display: 'flex', flexDirection: 'column', gap: '10px'}}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>收货人:</span>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '1px dashed #333',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
bottom: '3px',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant={'text'}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
|
||||
onClick={() => copyToClipboard(item.recipient)}
|
||||
>
|
||||
{item.recipient}
|
||||
<IconCopy />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>手机号码:</span>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '1px dashed #333',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
bottom: '3px',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant={'text'}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
|
||||
onClick={() => copyToClipboard(item.phone)}
|
||||
>
|
||||
{item.phone}
|
||||
<IconCopy />
|
||||
</Button>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span>所在地区:</span>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '1px dashed #333',
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
bottom: '3px',
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant={'text'}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
|
||||
onClick={() => copyToClipboard(item.address)}
|
||||
>
|
||||
{item.address}
|
||||
<IconCopy />
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { TextField, Button, Grid, Typography, Box, Card, CardMedia, CardActions, IconButton } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { toBase64 } from '@/helpers/toBase64';
|
||||
import axios from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
import PhotoCamera from '@mui/icons-material/PhotoCamera';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import { BASE_URI, BASE_URL } from '@/helpers/constants';
|
||||
import myAxios from '@/helpers/myAxios';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
|
||||
interface UserFormProps {
|
||||
apiUrl: string;
|
||||
}
|
||||
|
||||
const Input = styled('input')({
|
||||
display: 'none',
|
||||
});
|
||||
|
||||
const UserForm = () => {
|
||||
const { push } = useMyNavigation();
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [passportSeries, setPassportSeries] = useState('');
|
||||
const [pinfl, setPinfl] = useState('');
|
||||
const [passportFront, setPassportFront] = useState<File | null>(null);
|
||||
const [passportBack, setPassportBack] = useState<File | null>(null);
|
||||
const [passportFrontPreview, setPassportFrontPreview] = useState<string | null>(null);
|
||||
const [passportBackPreview, setPassportBackPreview] = useState<string | null>(null);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
if (!firstName) newErrors.firstName = 'Ism majburiy!';
|
||||
if (!lastName) newErrors.lastName = 'Familiya majburiy!';
|
||||
if (!passportSeries) newErrors.passportSeries = 'Passport seriya raqami majburiy!';
|
||||
if (!pinfl) newErrors.pinfl = 'PINFL (JSHSHR) majburiy!';
|
||||
if (!passportFront) newErrors.passportFront = 'Passport oldi rasmi majburiy!';
|
||||
if (!passportBack) newErrors.passportBack = 'Passport orqa rasmi majburiy!';
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handlePassportFrontChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0] || null;
|
||||
if (file) {
|
||||
setPassportFront(file);
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setPassportFrontPreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePassportBackChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0] || null;
|
||||
if (file) {
|
||||
setPassportBack(file);
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setPassportBackPreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const clearPassportFront = () => {
|
||||
setPassportFront(null);
|
||||
setPassportFrontPreview(null);
|
||||
};
|
||||
|
||||
const clearPassportBack = () => {
|
||||
setPassportBack(null);
|
||||
setPassportBackPreview(null);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!validateForm()) return;
|
||||
|
||||
try {
|
||||
const frontBase64 = passportFront ? await toBase64(passportFront) : '';
|
||||
const backBase64 = passportBack ? await toBase64(passportBack) : '';
|
||||
|
||||
const payload = {
|
||||
fullName: firstName + ' ' + lastName,
|
||||
passportSeries,
|
||||
passportPin: pinfl,
|
||||
passportFrontImage: frontBase64,
|
||||
passportBackImage: backBase64,
|
||||
};
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await axios.post(`${BASE_URI}/me/passports`, payload, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
toast.success("Ma'lumotlar muvaffaqiyatli yuborildi!");
|
||||
push('/profile/user-data');
|
||||
} catch (error) {
|
||||
console.error('API xatosi:', error);
|
||||
toast.error("Ma'lumotlar yuborishda xatolik yuz berdi!");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', padding: '20px' }}>
|
||||
<Typography variant='h5' align='center' gutterBottom>
|
||||
{"Foydalanuvchi ma'lumotlari"}
|
||||
</Typography>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label='Ism'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={firstName}
|
||||
onChange={e => setFirstName(e.target.value)}
|
||||
required
|
||||
error={!!errors.firstName}
|
||||
helperText={errors.firstName}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label='Familiya'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={lastName}
|
||||
onChange={e => setLastName(e.target.value)}
|
||||
required
|
||||
error={!!errors.lastName}
|
||||
helperText={errors.lastName}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label='Passport seriya raqami'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={passportSeries}
|
||||
onChange={e => setPassportSeries(e.target.value)}
|
||||
required
|
||||
error={!!errors.passportSeries}
|
||||
helperText={errors.passportSeries}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
label='PINFL (JSHSHR)'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={pinfl}
|
||||
onChange={e => setPinfl(e.target.value)}
|
||||
required
|
||||
error={!!errors.pinfl}
|
||||
helperText={errors.pinfl}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Card>
|
||||
<label htmlFor='passport-front-upload'>
|
||||
<Input accept='image/*' id='passport-front-upload' type='file' onChange={handlePassportFrontChange} />
|
||||
{passportFrontPreview ? (
|
||||
<CardMedia component='img' height='140' image={passportFrontPreview} alt='Passport front' />
|
||||
) : (
|
||||
<Button variant='outlined' component='span' fullWidth startIcon={<PhotoCamera />}>
|
||||
Passport oldi rasmi
|
||||
</Button>
|
||||
)}
|
||||
</label>
|
||||
{passportFrontPreview && (
|
||||
<CardActions>
|
||||
<IconButton onClick={clearPassportFront} aria-label='delete'>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<label htmlFor='passport-front-upload'>
|
||||
<IconButton component='span' aria-label='edit'>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</label>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
{errors.passportFront && (
|
||||
<Typography variant='caption' color='error'>
|
||||
{errors.passportFront}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Card>
|
||||
<label htmlFor='passport-back-upload'>
|
||||
<Input accept='image/*' id='passport-back-upload' type='file' onChange={handlePassportBackChange} />
|
||||
{passportBackPreview ? (
|
||||
<CardMedia component='img' height='140' image={passportBackPreview} alt='Passport back' />
|
||||
) : (
|
||||
<Button variant='outlined' component='span' fullWidth startIcon={<PhotoCamera />}>
|
||||
Passport orqa rasmi
|
||||
</Button>
|
||||
)}
|
||||
</label>
|
||||
{passportBackPreview && (
|
||||
<CardActions>
|
||||
<IconButton onClick={clearPassportBack} aria-label='delete'>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
<label htmlFor='passport-back-upload'>
|
||||
<IconButton component='span' aria-label='edit'>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</label>
|
||||
</CardActions>
|
||||
)}
|
||||
</Card>
|
||||
{errors.passportBack && (
|
||||
<Typography variant='caption' color='error'>
|
||||
{errors.passportBack}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button type='submit' variant='contained' color='primary' fullWidth>
|
||||
Yuborish
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserForm;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import IUserForm from './component/IUserForm';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
|
||||
return <IUserForm />;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Box, TextField, Button, Typography } from '@mui/material';
|
||||
|
||||
export default function Page() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
if (confirmPassword || error) setError(e.target.value !== confirmPassword);
|
||||
};
|
||||
|
||||
const handleConfirmPassword = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setConfirmPassword(e.target.value);
|
||||
setError(e.target.value !== password);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!error && password) {
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant='h6' fontWeight='bold' mb={2}>
|
||||
{"Parolni o'zgartirish"}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: '10px', alignItems: 'start' }}>
|
||||
<TextField fullWidth label='Yangi parol' type='password' value={password} onChange={handleChangePassword} margin='normal' />
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label='Parolni tasdiqlash'
|
||||
type='password'
|
||||
value={confirmPassword}
|
||||
onChange={handleConfirmPassword}
|
||||
margin='normal'
|
||||
error={error}
|
||||
helperText={error ? 'Parollar mos kelmadi!' : ''}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
transition: 'all 0.6s ease-out',
|
||||
opacity: password ? '1' : '0',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
fullWidth
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ width: '180px' }}
|
||||
onClick={handleSubmit}
|
||||
disabled={error || !password || !confirmPassword}
|
||||
>
|
||||
Tasdiqlash
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
72
src/app/[locale]/(views)/profile/(views)/settings/page.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Box, TextField, Button, Typography, useMediaQuery, useTheme } from '@mui/material';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
const links_list = [
|
||||
{
|
||||
title: "Passport qo'shish",
|
||||
path: '/add-passport',
|
||||
},
|
||||
{
|
||||
title: "Parolni o'zgartirish",
|
||||
path: '/edit-password',
|
||||
},
|
||||
];
|
||||
|
||||
const StyledButton = styled(Button)(({ theme }) => ({
|
||||
padding: theme.spacing(2, 4),
|
||||
borderRadius: theme.spacing(1),
|
||||
fontSize: '1rem',
|
||||
fontWeight: 'bold',
|
||||
boxShadow: theme.shadows[2],
|
||||
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
|
||||
backgroundColor: '#34495e',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: '#2c3e50',
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
// Kichik ekranlar uchun
|
||||
width: '100%',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export default function Page() {
|
||||
const { push } = useMyNavigation();
|
||||
const theme = useTheme();
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('md'));
|
||||
const sidebarWidth = isDesktop ? 197 : 0;
|
||||
const maxWidth = 1152;
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const navigate_handler = (url: string) => {
|
||||
push('/profile/settings' + url);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: `calc(100% - ${sidebarWidth}px)`,
|
||||
maxWidth: `${maxWidth}px`,
|
||||
margin: '0 auto',
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: isSmallScreen ? 'column' : 'row', // Kichik ekranda ustunli layout
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '20px',
|
||||
}}
|
||||
>
|
||||
<StyledButton onClick={() => navigate_handler('/add-passport')} variant='contained'>
|
||||
{"PASSPORT QO'SHISH"}
|
||||
</StyledButton>
|
||||
<StyledButton onClick={() => navigate_handler('/edit-password')} variant='contained'>
|
||||
{"PAROLNI O'ZGARTIRISH"}
|
||||
</StyledButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
378
src/app/[locale]/(views)/profile/(views)/user-data/page.tsx
Normal file
@@ -0,0 +1,378 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Button, Dialog, Grid, IconButton, Slide, Typography } from '@mui/material';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import useMainFetch from '@/hooks/useMainFetch';
|
||||
import { IUserData } from '@/app/[locale]/(views)/profile/components/IAboutUser';
|
||||
import Loader from '@/components/common/Loader';
|
||||
import { CloseIcon } from 'next/dist/client/components/react-dev-overlay/internal/icons/CloseIcon';
|
||||
import { useState } from 'react';
|
||||
|
||||
interface IPassport {
|
||||
active: boolean;
|
||||
fullName: string;
|
||||
passportBackImage: string;
|
||||
passportFrontImage: string;
|
||||
passportPin: string;
|
||||
passportSeries: string;
|
||||
}
|
||||
|
||||
const branches = [
|
||||
{ value: 'TASHKENT_CITY', label: 'Toshkent shahri' },
|
||||
{ value: 'FERGHANA', label: "Farg'ona viloyati" },
|
||||
{ value: 'SAMARQAND', label: 'Samarqand viloyati' },
|
||||
];
|
||||
|
||||
const Transition = (props: any) => <Slide direction='up' {...props} />;
|
||||
|
||||
const get_current_branches = (value: string | undefined) => branches.find(item => item.value === value)?.label || 'Toshkent';
|
||||
|
||||
export default function Page() {
|
||||
const { push } = useMyNavigation();
|
||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const handleImageClick = (image: string) => {
|
||||
setSelectedImage(image);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedImage(null);
|
||||
};
|
||||
|
||||
|
||||
const {data, isLoading} = useMainFetch({
|
||||
key: "client-data",
|
||||
endpoint: "/me"
|
||||
})
|
||||
const clientData = data?.data?.data
|
||||
|
||||
const passportsFetchData = useMainFetch({
|
||||
key: 'passports',
|
||||
endpoint: '/me/passports',
|
||||
generateData: res => res.data,
|
||||
});
|
||||
|
||||
const passportsData: IPassport[] = passportsFetchData?.data?.data || [];
|
||||
|
||||
const userFetchData = clientData as IUserData;
|
||||
|
||||
const userData = [
|
||||
// {
|
||||
// title: "To'liq ism",
|
||||
// value: userFetchData?.name,
|
||||
// },
|
||||
{
|
||||
title: 'Telefon raqam',
|
||||
value: userFetchData?.phone,
|
||||
},
|
||||
{
|
||||
title: 'Kargo ID',
|
||||
value: userFetchData?.aviaCargoId,
|
||||
},
|
||||
{
|
||||
title: "Tug'ilgan kun",
|
||||
value: userFetchData?.dateOfBirth,
|
||||
},
|
||||
{
|
||||
title: 'Adress',
|
||||
value: userFetchData?.address,
|
||||
},
|
||||
{
|
||||
title: 'Qulay filial',
|
||||
value: get_current_branches(userFetchData?.region),
|
||||
},
|
||||
];
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Box position={'relative'} sx={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
<Loader />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box p={5}>
|
||||
<Typography variant='h5' fontWeight='bold' mb={3}>
|
||||
{"Shaxsiy ma'lumotlar"}
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={0}>
|
||||
{userData.map((item, index) => (
|
||||
<Grid item xs={12} key={index} sx={{ borderBottom: '1px solid #e0e0e0', padding: '16px 0' }}>
|
||||
<Typography variant='subtitle2' sx={{ fontWeight: 600, color: '#333', marginBottom: '4px' }}>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant='body1' sx={{ color: '#555', fontSize: '1rem' }}>
|
||||
{item.value || '...'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Box sx={{ marginTop: '40px' }}>
|
||||
{passportsData.length > 0 && (
|
||||
<Box mt={3}>
|
||||
<Typography variant='h5' fontWeight='bold' mb={3}>
|
||||
{"Passport Ma'lumotlari"}
|
||||
</Typography>
|
||||
<Grid container spacing={3}>
|
||||
{passportsData.map((passport, index) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={index}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
p: 3,
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
border: '1px solid',
|
||||
borderColor: 'grey.400',
|
||||
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.03)',
|
||||
boxShadow: '0 6px 18px rgba(0,0,0,0.25)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography variant='h6' fontWeight='600' mb={2} color='text.primary'>
|
||||
{passport.fullName}
|
||||
</Typography>
|
||||
|
||||
<Grid container spacing={1} alignItems='center'>
|
||||
<Grid item xs={6}>
|
||||
<Box
|
||||
onClick={() => handleImageClick(passport.passportFrontImage)}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
aspectRatio: '4/3',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component='img'
|
||||
src={passport.passportFrontImage}
|
||||
alt='Passport oldi'
|
||||
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Box
|
||||
onClick={() => handleImageClick(passport.passportBackImage)}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
aspectRatio: '4/3',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component='img'
|
||||
src={passport.passportBackImage}
|
||||
alt='Passport orqa'
|
||||
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
margin: '20px 0 10px',
|
||||
}}
|
||||
>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Seriya
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '2px dotted #99999999',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
top: '7px',
|
||||
margin: '0 4px',
|
||||
}}
|
||||
></Box>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{passport.passportSeries}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
marginBottom: '10px',
|
||||
}}
|
||||
>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
PIN:
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '2px dotted #99999999',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
top: '7px',
|
||||
margin: '0 4px',
|
||||
}}
|
||||
></Box>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{passport.passportPin}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
Holati:
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: '2px dotted #99999999',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
top: '7px',
|
||||
margin: '0 4px',
|
||||
}}
|
||||
></Box>
|
||||
<Typography variant='body2' color='text.secondary'>
|
||||
{passport.active ? 'Faol' : 'Faolmas'}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{/*<Box marginTop={'14px'}>*/}
|
||||
{/* {passportsData.length > 0 && (*/}
|
||||
{/* <Box marginTop={'24px'}>*/}
|
||||
{/* <Typography variant='h6' fontWeight='bold' mb={2}>*/}
|
||||
{/* {"Passport Ma'lumotlari"}*/}
|
||||
{/* </Typography>*/}
|
||||
|
||||
{/* <Grid container spacing={3}>*/}
|
||||
{/* {passportsData.map((passport, index) => (*/}
|
||||
{/* <Grid item xs={12} sm={6} md={4} key={index}>*/}
|
||||
{/* <Box*/}
|
||||
{/* border={'1px solid #ddd'}*/}
|
||||
{/* borderRadius={'8px'}*/}
|
||||
{/* padding={'16px'}*/}
|
||||
{/* boxShadow={'0 2px 4px rgba(0, 0, 0, 0.1)'}*/}
|
||||
{/* >*/}
|
||||
{/* <Typography variant='subtitle1' fontWeight='bold' gutterBottom>*/}
|
||||
{/* {passport.fullName}*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <Typography variant='body2' color='gray'>*/}
|
||||
{/* Seriya: {passport.passportSeries}*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <Typography variant='body2' color='gray'>*/}
|
||||
{/* PIN: {passport.passportPin}*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <Typography variant='body2' color='gray'>*/}
|
||||
{/* Faol: {passport.active ? 'Ha' : "Yo'q"}*/}
|
||||
{/* </Typography>*/}
|
||||
{/* <Box sx={{ display: 'flex', gap: '8px', marginTop: '10px', justifyContent: 'center' }}>*/}
|
||||
{/* <Box onClick={() => handleImageClick(passport.passportFrontImage)}>*/}
|
||||
{/* <Box*/}
|
||||
{/* component='img'*/}
|
||||
{/* src={passport.passportFrontImage}*/}
|
||||
{/* sx={{*/}
|
||||
{/* width: '80px',*/}
|
||||
{/* height: '80px',*/}
|
||||
{/* objectFit: 'contain',*/}
|
||||
{/* cursor: 'pointer',*/}
|
||||
{/* }}*/}
|
||||
{/* alt={'Passport oldi rasmi'}*/}
|
||||
{/* />*/}
|
||||
{/* </Box>*/}
|
||||
{/* <Box onClick={() => handleImageClick(passport.passportBackImage)}>*/}
|
||||
{/* <Box*/}
|
||||
{/* component='img'*/}
|
||||
{/* src={passport.passportBackImage}*/}
|
||||
{/* sx={{*/}
|
||||
{/* width: '80px',*/}
|
||||
{/* height: '80px',*/}
|
||||
{/* objectFit: 'contain',*/}
|
||||
{/* cursor: 'pointer',*/}
|
||||
{/* }}*/}
|
||||
{/* alt={'Passport orqa taraf rasmi'}*/}
|
||||
{/* />*/}
|
||||
{/* </Box>*/}
|
||||
{/* </Box>*/}
|
||||
{/* </Box>*/}
|
||||
{/* </Grid>*/}
|
||||
{/* ))}*/}
|
||||
{/* </Grid>*/}
|
||||
{/* </Box>*/}
|
||||
{/* )}*/}
|
||||
{/*</Box>*/}
|
||||
|
||||
<Dialog
|
||||
open={isModalOpen}
|
||||
TransitionComponent={Transition}
|
||||
keepMounted
|
||||
onClose={handleModalClose}
|
||||
aria-describedby='alert-dialog-slide-description'
|
||||
fullWidth
|
||||
>
|
||||
<IconButton
|
||||
aria-label='close'
|
||||
onClick={handleModalClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
color: theme => theme.palette.grey[500],
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
{selectedImage && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: '24px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
component='img'
|
||||
src={selectedImage}
|
||||
sx={{
|
||||
maxWidth: '100%',
|
||||
maxHeight: '80vh',
|
||||
objectFit: 'contain',
|
||||
}}
|
||||
alt={'Kattalashtirilgan rasm'}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Dialog>
|
||||
{/*<Box sx={{ display: 'flex', justifyContent: 'end', marginTop: '8px' }}>*/}
|
||||
{/* <Button onClick={editHandler} sx={{ textTransform: 'capitalize' }}>*/}
|
||||
{/* {'Tahrirlash'}*/}
|
||||
{/* </Button>*/}
|
||||
{/*</Box>*/}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
89
src/app/[locale]/(views)/profile/components/IAboutUser.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Typography, Avatar, Divider, Button } from '@mui/material';
|
||||
import { IconExit } from '@/components/common/Icons';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import useMainFetch from '@/hooks/useMainFetch';
|
||||
|
||||
export interface IUserData {
|
||||
name: string;
|
||||
phone: string;
|
||||
passportSeries: string;
|
||||
pinfl: string;
|
||||
dateOfBirth: string;
|
||||
address: string;
|
||||
region: string;
|
||||
passportFront: string;
|
||||
passportBack: string;
|
||||
aviaCargoId: string | null;
|
||||
autoCargoId: string | null;
|
||||
registered: boolean;
|
||||
}
|
||||
|
||||
export default function IAboutUser() {
|
||||
const router = useRouter();
|
||||
|
||||
const exitHandler = () => {
|
||||
router.push('/');
|
||||
localStorage.clear();
|
||||
};
|
||||
|
||||
const fetchData = useMainFetch({
|
||||
key: 'user-data',
|
||||
endpoint: '/me',
|
||||
generateData: res => res.data.data,
|
||||
});
|
||||
|
||||
const userData = fetchData.data as IUserData;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Box sx={{ p: { xs: 2, sm: 3, md: 4 }, mx: 'auto' }}>
|
||||
<Box display={'flex'} position={'relative'} justifyContent={{ xs: 'center', md: 'space-between' }} alignItems={'center'}>
|
||||
<Box
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
flexDirection={{ xs: 'column', md: 'row' }}
|
||||
textAlign={{ xs: 'center', md: 'left' }}
|
||||
mb={3}
|
||||
>
|
||||
{/*<Avatar*/}
|
||||
{/* src='https://images.unsplash.com/photo-1438761681033-6461ffad8d80?fm=jpg&q=60&w=3000&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8ZmVtYWxlJTIwcHJvZmlsZXxlbnwwfHwwfHx8MA%3D%3D'*/}
|
||||
{/* alt='Helen Zhou'*/}
|
||||
{/* sx={{ width: 80, height: 80, mr: { sm: 2, xs: 0 }, mb: { xs: 1, sm: 0 } }}*/}
|
||||
{/*/>*/}
|
||||
<Box>
|
||||
<Typography variant='h5' fontWeight='bold'>
|
||||
{userData?.name}
|
||||
</Typography>
|
||||
{/*<Typography color='green'>Aktiv</Typography>*/}
|
||||
<Typography color={userData?.registered ? "green" : "red"}>
|
||||
{userData?.registered ? "Aktiv" : "Noaktiv"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
{/*<Button onClick={exitHandler} variant={'text'} color={"inherit"} sx={{display: 'flex', alignItems: 'center', gap: '5px'}}>*/}
|
||||
<Button
|
||||
onClick={exitHandler}
|
||||
variant={'text'}
|
||||
color={'inherit'}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
opacity: '0.7',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
position: { xs: 'absolute' },
|
||||
right: '0',
|
||||
top: { xs: '0', md: '18px' },
|
||||
}}
|
||||
>
|
||||
<IconExit />
|
||||
<Box display={{ xs: 'none', md: 'inline-block' }}>Chiqish</Box>
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ mb: { xs: 0 } }} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
37
src/app/[locale]/(views)/profile/components/ISidebar.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import { Box, List, ListItemButton, ListItemText } from '@mui/material';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { IconCard, IconPage, IconProducts, IconSettings } from '@/components/common/Icons';
|
||||
|
||||
const menuItems = [
|
||||
{ value: '', label: 'Mahsulotlar', icon: IconProducts },
|
||||
{ value: '/user-data', label: "Ma'lumotlarim", icon: IconPage },
|
||||
// { value: '/id-and-address', label: "ID / Adres", icon: IconCard },
|
||||
{ value: '/settings', label: "Sozlamalar", icon: IconSettings },
|
||||
];
|
||||
|
||||
export function ISidebar({ locale }: { locale: string }) {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
// <Box width={{xs: 86, md: 240}} bgcolor='#fff' p={2}>
|
||||
<Box display={{xs: 'flex', md: 'block'}} bgcolor='#fff' p={2}>
|
||||
{/*<List disablePadding>*/}
|
||||
<List disablePadding sx={{display: {xs: 'flex', md: 'inline-block'}, marginX: 'auto'}}>
|
||||
{menuItems.map(item => (
|
||||
<ListItemButton
|
||||
key={item.value}
|
||||
href={`/${locale}/profile/${item.value}`}
|
||||
selected={pathname.split('profile').at(-1) === item.value}
|
||||
sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}
|
||||
>
|
||||
{!!item.icon && <item.icon color={pathname.split('profile').at(-1) === item.value ? '#758CA3' : '#777'} />}
|
||||
<ListItemText primary={item.label} sx={{ fontWeight: 500, display: {xs: "none", md: "block" }}} />
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/app/[locale]/(views)/profile/layout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
import { ISidebar } from '@/app/[locale]/(views)/profile/components/ISidebar';
|
||||
import LandingLayout from '@/components/layout/landing-layout';
|
||||
import IAboutUser from '@/app/[locale]/(views)/profile/components/IAboutUser';
|
||||
import Container from '@/components/common/Container';
|
||||
|
||||
export default function Layout({ children, params }: { children: React.ReactNode; params: { locale: string } }) {
|
||||
return (
|
||||
<LandingLayout>
|
||||
<Container>
|
||||
<IAboutUser />
|
||||
{/*<Box display='flex' bgcolor='#f8f8f8' minHeight='50vh'>*/}
|
||||
<Box display='flex' flexDirection={{xs: 'column', md: 'row'}} bgcolor='#f8f8f8' minHeight='50vh'>
|
||||
<ISidebar locale={params.locale} />
|
||||
|
||||
<Box flex={1}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
</Container>
|
||||
</LandingLayout>
|
||||
);
|
||||
}
|
||||
150
src/app/[locale]/(views)/profile/page.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Tabs, Tab, Card, CardMedia, CardContent, Typography, Button, Chip, Avatar, Stack } from '@mui/material';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import Loader from '@/components/common/Loader';
|
||||
import useMainFetch from '@/hooks/useMainFetch';
|
||||
import { IUserData } from '@/app/[locale]/(views)/profile/components/IAboutUser';
|
||||
import { ColumnData, MyTable } from '@/components/common/MyTable';
|
||||
import BasePagination from '@/components/ui-kit/BasePagination';
|
||||
import { DEFAULT_PAGE_SIZE } from '@/helpers/constants';
|
||||
import { PartyStatusList } from '@/data/party/party.model';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
|
||||
interface IProduct {
|
||||
id: string;
|
||||
partyName: string;
|
||||
boxName: string;
|
||||
cargoId: string;
|
||||
trekId: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
weight: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
status: string;
|
||||
hasImage: boolean;
|
||||
imageUrl: string;
|
||||
packetName: string;
|
||||
}
|
||||
|
||||
interface IProductData {
|
||||
data: {
|
||||
data: IProduct[];
|
||||
totalPages: number;
|
||||
totalElements: number;
|
||||
};
|
||||
}
|
||||
|
||||
export default function ProfilePage() {
|
||||
const t = useMyTranslation();
|
||||
const { replace } = useMyNavigation();
|
||||
const [category, setCategory] = useState('barchasi');
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
|
||||
|
||||
const fetchProductsData = useMainFetch({
|
||||
key: 'products-data',
|
||||
endpoint: '/me/products',
|
||||
generateData: res => res.data,
|
||||
});
|
||||
|
||||
const fetchUserData = useMainFetch({
|
||||
key: 'user-data',
|
||||
endpoint: '/me',
|
||||
generateData: res => res.data,
|
||||
});
|
||||
|
||||
const columns: ColumnData<IProduct>[] = [
|
||||
{
|
||||
dataKey: 'id',
|
||||
label: 'No',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
dataKey: 'name',
|
||||
label: 'Nomi',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
dataKey: 'trekId',
|
||||
label: 'TrekID',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
dataKey: 'status',
|
||||
label: 'Status',
|
||||
width: 160,
|
||||
renderCell: data => {
|
||||
return <Box width={160}>{t(data.status)}</Box>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataKey: 'packetName',
|
||||
label: 'Paket',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
dataKey: 'amount',
|
||||
label: 'Miqdori',
|
||||
width: 120,
|
||||
},
|
||||
// {
|
||||
// dataKey: 'price',
|
||||
// label: 'Narxi',
|
||||
// width: 120,
|
||||
// },
|
||||
// {
|
||||
// dataKey: 'totalPrice',
|
||||
// label: 'Jami narxi',
|
||||
// width: 120,
|
||||
// },
|
||||
{
|
||||
dataKey: 'weight',
|
||||
label: t("weight"),
|
||||
width: 70,
|
||||
},
|
||||
];
|
||||
|
||||
const productsData = fetchProductsData.data as IProductData | undefined;
|
||||
|
||||
const userData = fetchUserData.data as IUserData | undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (userData?.registered === false) {
|
||||
replace('/profile/user-data');
|
||||
}
|
||||
}, [userData?.registered]);
|
||||
|
||||
const handleChange = (newPage: number) => {
|
||||
setTimeout(() => {
|
||||
setPage(newPage);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
if (fetchUserData?.isLoading || fetchProductsData?.isLoading || !productsData)
|
||||
return (
|
||||
<Box position={'relative'} sx={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center' }}>
|
||||
<Loader />
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Box mt={2} display='flex' flexDirection='column' gap={2}>
|
||||
<Box mb={6}>
|
||||
<MyTable columns={columns} data={productsData?.data?.data} loading={false} />
|
||||
</Box>
|
||||
<Stack direction={'row'} justifyContent={'center'}>
|
||||
<BasePagination
|
||||
page={page}
|
||||
pageSize={pageSize}
|
||||
totalCount={productsData?.data?.totalPages || 0}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/customers/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardClientsPage from '@/routes/private/clients/DashboardClientsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardClientsPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/items/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardGoodsPage from '@/routes/private/items/DashboardItemsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardGoodsPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/layout.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardLayout from '@/components/layout/dashboard-layout';
|
||||
|
||||
export default function RootLayout({ children, params: { locale } }: { children?: React.ReactNode; params: { locale: string } }) {
|
||||
return <DashboardLayout>{children}</DashboardLayout>;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/loading.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||
<CircularProgress size={'64px'} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/main/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardHome from '@/routes/private/dashboard-home';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardHome />;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/packets/create/loading.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||
<CircularProgress size={'64px'} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
22
src/app/[locale]/dashboard/packets/create/page.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import DashboardCreateBoxPage from '@/routes/private/boxes-create/DashboardCreateBox';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { party_requests } from '@/data/party/party.requests';
|
||||
import Loader from '@/components/common/Loader';
|
||||
import React from 'react';
|
||||
|
||||
export default function Home() {
|
||||
const partiesData = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
|
||||
selectData(data) {
|
||||
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
|
||||
},
|
||||
placeholderData: [],
|
||||
});
|
||||
|
||||
if (partiesData.loading || !partiesData.data) {
|
||||
return <Loader p={8} size={96} />;
|
||||
}
|
||||
|
||||
return <DashboardCreateBoxPage partiesData={partiesData.data} />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||
<CircularProgress size={'64px'} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import DashboardEditBoxPage from '@/routes/private/boxes-create/DashboardEditBox';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardEditBoxPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/packets/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardBoxesPage from '@/routes/private/boxes/DashboardBoxesPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardBoxesPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardHome from '@/routes/private/dashboard-home';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardHome />;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/parties/create/loading.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||
<CircularProgress size={'64px'} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/parties/create/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardCreatePartyPage from '@/routes/private/parties-create/DashboardCreatePartyPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardCreatePartyPage />;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
|
||||
export default function DashboardLoading() {
|
||||
return (
|
||||
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
|
||||
<CircularProgress size={'64px'} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import DashboardEditPartyPage from '@/routes/private/parties-create/DashboardEditPartyPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardEditPartyPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/parties/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardPartiesPage from '@/routes/private/parties/DashboardPartiesPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardPartiesPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/staffs/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardStaffsPage from '@/routes/private/staffs/DashboardStaffsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardStaffsPage />;
|
||||
}
|
||||
82
src/app/[locale]/globals.css
Normal file
@@ -0,0 +1,82 @@
|
||||
:root {
|
||||
--app-height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
scroll-behavior: smooth;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
background-color: #fff;
|
||||
color: #555351;
|
||||
font-family: "Inter", "Arial", sans-serif !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.dashboard-layout {
|
||||
font-family: 'SF Pro Display', sans-serif !important;
|
||||
}
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
/* *:focus-visible {
|
||||
outline: 2px dashed #479aff;
|
||||
outline-offset: 3px;
|
||||
} */
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.no-select {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------- Number input ni chiziqchalarini ko'rsatmaslik ------------------ */
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.brd {
|
||||
border: 1px solid red !important;
|
||||
}
|
||||
133
src/app/[locale]/layout.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import './globals.css';
|
||||
import './styles.css';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/autoplay';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import 'aos/dist/aos.css';
|
||||
|
||||
// import type { Metadata } from 'next';
|
||||
import { MainProvider } from '@/providers';
|
||||
|
||||
// export const metadata: Metadata = {
|
||||
// title: 'CPost-Express',
|
||||
// description: 'CPost-Express',
|
||||
// };
|
||||
|
||||
export default function RootLayout({ children, params: { locale } }: { children?: React.ReactNode; params: { locale: string } }) {
|
||||
const siteLanguage = locale || 'ru';
|
||||
|
||||
const seoRu = {
|
||||
title: 'Cpost express: Авиапочта из Китая в Узбекистан',
|
||||
desc: 'Cpost express ✈️Авиапочта из Китая в Узбекистан, ⏳Срок доставки 5-14 дней, 📦Минимальный заказ 0 кг, 📷Фото отчёт, 🛃Гарантия сохранности посылок, ☎️ +998 90-113-44-77 админ',
|
||||
keywords:
|
||||
'Cpost express, Авиапочта, Китай, Узбекистан, доставка, срок доставки, минимальный заказ, фото отчёт, гарантия сохранности, посылки, контакты, админ',
|
||||
img: 'https://cpost-express.uz/logo.jpeg',
|
||||
};
|
||||
const seoUz = {
|
||||
title: "Cpost express: Xitoydan O'zbekistonga aviapochta",
|
||||
desc: "Cpost express ✈️Xitoydan O'zbekistonga aviapoçta, ⏳Yetkazib berish muddati 5-14 kun, 📦Minimal buyurtma 0 kg, 📷Surat maqola, 🛃Poçta xavfsizligi kafolati, ☎️ +998 90-113-44-77 admin",
|
||||
keywords:
|
||||
"Cpost express, Aviapoçta, Xitoy, O'zbekiston, yetkazib berish, yetkazib berish muddati, minimal buyurtma, surat maqola, poçta xavfsizligi kafolati, aloqa ma'lumotlari, admin",
|
||||
img: 'https://cpost-express.uz/logo.jpeg',
|
||||
};
|
||||
const seoEn = {
|
||||
title: 'Cpost express: Air Mail from China to Uzbekistan',
|
||||
desc: 'Cpost express ✈️Air Mail from China to Uzbekistan, ⏳Delivery time 5-14 days, 📦Minimum order 0 kg, 📷Photo report, 🛃Guarantee of parcel safety, ☎️ +998 90-113-44-77 admin',
|
||||
keywords:
|
||||
'Cpost express, Air mail, China, Uzbekistan, delivery, delivery time, minimum order, photo report, guarantee of parcel safety, contacts, admin',
|
||||
img: 'https://cpost-express.uz/logo.jpeg',
|
||||
};
|
||||
|
||||
let seo = seoRu;
|
||||
switch (siteLanguage) {
|
||||
case 'ru': {
|
||||
seo = seoRu;
|
||||
break;
|
||||
}
|
||||
case 'uz': {
|
||||
seo = seoUz;
|
||||
break;
|
||||
}
|
||||
case 'en': {
|
||||
seo = seoEn;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
seo = seoRu;
|
||||
}
|
||||
}
|
||||
|
||||
const alternates = [
|
||||
{
|
||||
link: `https://cpost-express.uz/ru`,
|
||||
hreflang: 'ru',
|
||||
},
|
||||
{
|
||||
link: `https://cpost-express.uz/en`,
|
||||
hreflang: 'en',
|
||||
},
|
||||
{
|
||||
link: `https://cpost-express.uz/uz`,
|
||||
hreflang: 'uz',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<html lang={siteLanguage}>
|
||||
<head>
|
||||
<title>{seo.title}</title>
|
||||
<meta name='description' content={seo.desc} />
|
||||
<meta name='keywords' content={seo.keywords} />
|
||||
|
||||
<meta property='og:type' content='website' />
|
||||
<meta property='og:title' content={seo.title} />
|
||||
<meta property='og:description' content={seo.desc} />
|
||||
<meta property='og:url' content={seo.img} />
|
||||
|
||||
{alternates
|
||||
.filter((i, index) => {
|
||||
if (siteLanguage === 'ru') {
|
||||
return index !== 0;
|
||||
}
|
||||
if (siteLanguage === 'en') {
|
||||
return index !== 1;
|
||||
}
|
||||
if (siteLanguage === 'uz') {
|
||||
return index !== 2;
|
||||
}
|
||||
})
|
||||
.map(({ link, hreflang }) => {
|
||||
return <link rel='alternate' href={link} hrefLang={hreflang} key={hreflang} />;
|
||||
})}
|
||||
|
||||
<link rel='preconnect' href='https://fonts.googleapis.com' />
|
||||
<link rel='preconnect' href='https://fonts.gstatic.com' crossOrigin='' />
|
||||
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap' rel='stylesheet' />
|
||||
<link href='https://fonts.cdnfonts.com/css/sf-pro-display' rel='stylesheet' />
|
||||
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1' />
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/apple-touch-icon.png' />
|
||||
<link rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png' />
|
||||
<link rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png' />
|
||||
<link rel='manifest' href='/site.webmanifest' />
|
||||
</head>
|
||||
<body>
|
||||
<MainProvider>{children}</MainProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
// export async function generateStaticParams() {
|
||||
// return [
|
||||
// {
|
||||
// locale: 'uz',
|
||||
// },
|
||||
// {
|
||||
// locale: 'ru',
|
||||
// },
|
||||
// {
|
||||
// locale: 'en',
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
95
src/app/[locale]/login/page.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Box, FormControl, FormLabel, Paper, Stack, TextField, Typography } from '@mui/material';
|
||||
import Container from '@/components/common/Container';
|
||||
import useInput from '@/hooks/useInput';
|
||||
import { notifyUnknownError } from '@/services/notification';
|
||||
import { user_requests } from '@/data/user/user.requests';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const LoginPage = (props: Props) => {
|
||||
const locale = useLocale();
|
||||
const navigation = useMyNavigation();
|
||||
const { login } = useAuth();
|
||||
const loginInput = useInput('');
|
||||
const passInput = useInput('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onSubmit = async (event: any) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (loginInput.value && passInput.value) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await user_requests.login({
|
||||
username: loginInput.value,
|
||||
password: passInput.value,
|
||||
});
|
||||
login({ accessToken: response.data.data.accessToken });
|
||||
navigation.push(pageLinks.dashboard.main.index);
|
||||
} catch (error) {
|
||||
notifyUnknownError(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '100vh',
|
||||
backgroundColor: '#f1f1f1',
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 5,
|
||||
maxWidth: 400,
|
||||
margin: '0 auto',
|
||||
borderRadius: '16px',
|
||||
}}
|
||||
component={'form'}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Stack direction={'row'} justifyContent={'center'} alignItems={'center'} mb={3}>
|
||||
<Typography sx={{ fontSize: '32px', fontWeight: 700, color: '#3489e4' }}>
|
||||
Login
|
||||
{/* <NextLink href={pageLinks.home}>CPost</NextLink> */}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={2} mb={2}>
|
||||
<label>
|
||||
<FormLabel>Login</FormLabel>
|
||||
<TextField type='text' name='login' onChange={loginInput.onChange} value={loginInput.value} fullWidth />
|
||||
</label>
|
||||
<label>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<TextField type='password' name='password' onChange={passInput.onChange} value={passInput.value} fullWidth />
|
||||
</label>
|
||||
</Stack>
|
||||
|
||||
<BaseButton onClick={onSubmit} loading={loading} fullWidth size='large'>
|
||||
Send
|
||||
</BaseButton>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
||||
10
src/app/[locale]/news/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import Layout from '@/components/layout/landing-layout';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const NewsPage = (props: Props) => {
|
||||
return <Layout>NewsPage</Layout>;
|
||||
};
|
||||
|
||||
export default NewsPage;
|
||||
10
src/app/[locale]/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import Layout from '@/components/layout/landing-layout';
|
||||
import Homepage from '@/routes/public/homepage';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<Layout>
|
||||
<Homepage />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
14
src/app/[locale]/styles.css
Normal file
@@ -0,0 +1,14 @@
|
||||
.hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.transition {
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
.social-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
18
src/clientStore/StoreProvider.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import { type PropsWithChildren, useRef } from 'react';
|
||||
import type { StoreInterface, StoreType } from './store';
|
||||
import { initializeStore, Provider } from './store';
|
||||
|
||||
// export interface PreloadedStoreInterface extends Pick<StoreInterface, 'lastUpdate'> {}
|
||||
export interface PreloadedStoreInterface {}
|
||||
|
||||
export default function StoreProvider({ children, ...props }: PropsWithChildren<PreloadedStoreInterface>) {
|
||||
const storeRef = useRef<StoreType>();
|
||||
|
||||
if (!storeRef.current) {
|
||||
storeRef.current = initializeStore(props);
|
||||
}
|
||||
|
||||
return <Provider value={storeRef.current}>{children}</Provider>;
|
||||
}
|
||||
59
src/clientStore/store.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { createStore, useStore as useZustandStore } from 'zustand';
|
||||
import { PreloadedStoreInterface } from './StoreProvider';
|
||||
|
||||
export interface StoreInterface {
|
||||
lastUpdate: number;
|
||||
light: boolean;
|
||||
count: number;
|
||||
tick: (lastUpdate: number) => void;
|
||||
increment: () => void;
|
||||
decrement: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
function getDefaultInitialState() {
|
||||
return {
|
||||
lastUpdate: new Date(1970, 1, 1).getTime(),
|
||||
light: false,
|
||||
count: 0,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type StoreType = ReturnType<typeof initializeStore>;
|
||||
|
||||
const storeContext = createContext<StoreType | null>(null);
|
||||
|
||||
export const Provider = storeContext.Provider;
|
||||
|
||||
export function useStore<T>(selector: (state: StoreInterface) => T) {
|
||||
const store = useContext(storeContext);
|
||||
|
||||
if (!store) throw new Error('Store is missing the provider');
|
||||
|
||||
return useZustandStore(store, selector);
|
||||
}
|
||||
|
||||
export function initializeStore(preloadedState: PreloadedStoreInterface) {
|
||||
return createStore<StoreInterface>((set, get) => ({
|
||||
...getDefaultInitialState(),
|
||||
...preloadedState,
|
||||
tick: lastUpdate =>
|
||||
set({
|
||||
lastUpdate,
|
||||
light: !get().light,
|
||||
}),
|
||||
increment: () =>
|
||||
set({
|
||||
count: get().count + 1,
|
||||
}),
|
||||
decrement: () =>
|
||||
set({
|
||||
count: get().count - 1,
|
||||
}),
|
||||
reset: () =>
|
||||
set({
|
||||
count: getDefaultInitialState().count,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
20
src/clientStore/useInterval.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
||||
export default function useInterval(callback: () => void, delay: number | undefined) {
|
||||
const savedCallback = useRef<typeof callback>();
|
||||
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => savedCallback.current?.();
|
||||
|
||||
if (delay !== null) {
|
||||
const id = setInterval(handler, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
}, [delay]);
|
||||
}
|
||||
146
src/components/common/ActionPopMenu/index.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import * as React from 'react';
|
||||
import { styled, alpha } from '@mui/material/styles';
|
||||
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { CircularProgress, IconButton, PopoverOrigin, SvgIcon, Typography } from '@mui/material';
|
||||
import { MoreVert } from '@mui/icons-material';
|
||||
|
||||
type PlacementType = {
|
||||
anchorOrigin?: PopoverOrigin;
|
||||
transformOrigin?: PopoverOrigin;
|
||||
};
|
||||
|
||||
const StyledMenu = styled((props: MenuProps & { placement: Required<PlacementType> }) => (
|
||||
<Menu elevation={0} anchorOrigin={props.placement.anchorOrigin} transformOrigin={props.placement.transformOrigin} {...props} />
|
||||
))(({ theme }) => ({
|
||||
'& .MuiPaper-root': {
|
||||
borderRadius: 6,
|
||||
marginTop: theme.spacing(1),
|
||||
minWidth: 180,
|
||||
color: theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[300],
|
||||
boxShadow:
|
||||
'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
|
||||
'& .MuiMenu-list': {
|
||||
padding: '4px 0',
|
||||
},
|
||||
'& .MuiMenuItem-root': {
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: 18,
|
||||
color: theme.palette.text.secondary,
|
||||
marginRight: theme.spacing(1.5),
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
type ActionPopMenuProps = {
|
||||
mainIcon?: React.ReactNode;
|
||||
buttons: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
dontCloseOnClick?: boolean;
|
||||
}[];
|
||||
placement?: PlacementType;
|
||||
};
|
||||
|
||||
export default function ActionPopMenu({ buttons, mainIcon, placement }: ActionPopMenuProps) {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<IconButton
|
||||
id='demo-customized-button'
|
||||
aria-controls={open ? 'demo-customized-menu' : undefined}
|
||||
aria-haspopup='true'
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={handleClick}
|
||||
color='primary'
|
||||
>
|
||||
{mainIcon ? mainIcon : <MoreVert />}
|
||||
</IconButton>
|
||||
<StyledMenu
|
||||
id='demo-customized-menu'
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'demo-customized-button',
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
onClick={e => e.stopPropagation()}
|
||||
placement={{
|
||||
anchorOrigin: placement?.anchorOrigin ?? {
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
},
|
||||
transformOrigin: placement?.transformOrigin ?? {
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
'.MuiMenu-list': {
|
||||
padding: '4px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{buttons.map((btn, index) => {
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={event => {
|
||||
event.stopPropagation();
|
||||
btn.onClick();
|
||||
if (!btn.dontCloseOnClick) handleClose();
|
||||
}}
|
||||
disableRipple
|
||||
key={index}
|
||||
sx={{
|
||||
padding: '8px 12px',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
{btn.loading ? (
|
||||
<CircularProgress size={16} />
|
||||
) : (
|
||||
<SvgIcon
|
||||
sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{btn.icon}
|
||||
</SvgIcon>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '16px',
|
||||
letterSpacing: '-0.4px',
|
||||
}}
|
||||
>
|
||||
{btn.label}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</StyledMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
150
src/components/common/AttachInvoiceModal/index.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||
import BaseModal from '@/components/ui-kit/BaseModal';
|
||||
import BaseReactSelect from '@/components/ui-kit/BaseReactSelect';
|
||||
import { box_requests } from '@/data/box/box.requests';
|
||||
import { customer_requests } from '@/data/customers/customer.requests';
|
||||
import { invoice_requests } from '@/data/invoice/invoice.requests';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import useRequest from '@/hooks/useRequest';
|
||||
import { notifyUnknownError } from '@/services/notification';
|
||||
import { Box, Stack, Typography, styled } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
.title {
|
||||
color: #000;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #5d5850;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
onSuccess: () => void;
|
||||
defaultValues?: {
|
||||
clientId?: number;
|
||||
packetId?: number;
|
||||
};
|
||||
};
|
||||
|
||||
const AttachInvoiceModal = ({ onClose, open, onSuccess, defaultValues }: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const getClientsQuery = useRequest(() => customer_requests.getAll(), {
|
||||
selectData(data) {
|
||||
return data.data.data.data.map(i => ({ label: i.fullName, value: i.id }));
|
||||
},
|
||||
});
|
||||
|
||||
const getBoxesQuery = useRequest(() => box_requests.getAll(), {
|
||||
selectData(data) {
|
||||
return data.data.data.data.map(i => ({ value: i.id, label: i.name }));
|
||||
},
|
||||
});
|
||||
|
||||
const clientsList = getClientsQuery.data || [];
|
||||
const boxesList = getBoxesQuery.data || [];
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
} = useForm<{
|
||||
clientId: number;
|
||||
packetId: number;
|
||||
}>({
|
||||
defaultValues: {
|
||||
...defaultValues,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async values => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await invoice_requests.create({ ...values });
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
notifyUnknownError(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<BaseModal maxWidth='400px' onClose={onClose} open={open}>
|
||||
<StyledBox component={'form'} onSubmit={onSubmit}>
|
||||
<Typography className='title'>{t('create_invoice')}</Typography>
|
||||
|
||||
<Stack spacing={3.5}>
|
||||
<Box>
|
||||
<Typography className='label'>{t('role')}</Typography>
|
||||
<Controller
|
||||
name='clientId'
|
||||
control={control}
|
||||
render={({ field, fieldState, formState }) => {
|
||||
return (
|
||||
<BaseReactSelect
|
||||
value={clientsList.find(p => p.value === field.value)}
|
||||
onChange={(newValue: any) => {
|
||||
field.onChange(newValue.value);
|
||||
}}
|
||||
onBlur={field.onBlur}
|
||||
name={field.name}
|
||||
options={clientsList}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography className='label'>{t('role')}</Typography>
|
||||
<Controller
|
||||
name='packetId'
|
||||
control={control}
|
||||
render={({ field, fieldState, formState }) => {
|
||||
return (
|
||||
<BaseReactSelect
|
||||
value={boxesList.find(p => p.value === field.value)}
|
||||
onChange={(newValue: any) => {
|
||||
field.onChange(newValue.value);
|
||||
}}
|
||||
onBlur={field.onBlur}
|
||||
name={field.name}
|
||||
options={boxesList}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Stack direction={'row'} justifyContent={'flex-start'} alignItems={'center'} spacing={3}>
|
||||
<BaseButton colorVariant='blue' type='submit' loading={loading}>
|
||||
{t('create')}
|
||||
</BaseButton>
|
||||
<BaseButton variant='outlined' type='button' colorVariant='blue-outlined' disabled={loading} onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</BaseButton>
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
</BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttachInvoiceModal;
|
||||
220
src/components/common/Calculator/Calculator.tsx
Normal file
@@ -0,0 +1,220 @@
|
||||
'use client';
|
||||
|
||||
import BaseModal from '@/components/ui-kit/BaseModal';
|
||||
import useInput from '@/hooks/useInput';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { Box, Grid, Modal, Stack, Typography, styled } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.checkboxes-grid-wrapper {
|
||||
max-width: 300px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkboxes-grid {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.checkboxes-grid-label {
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid #b2b2b2;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
&:focus-within,
|
||||
&:has(input:checked) {
|
||||
border: 1px solid #2d97ff;
|
||||
box-shadow: 0 0 0 1px #2d97ff;
|
||||
}
|
||||
}
|
||||
|
||||
.calc-title {
|
||||
color: #11579b;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 28px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.calc-subtitle {
|
||||
color: #1e1e1e;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.calc-input {
|
||||
border-radius: 5.352px;
|
||||
border: 1.338px solid rgba(17, 87, 155, 0.6);
|
||||
background: #fff;
|
||||
padding: 8px 10px;
|
||||
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16.056px;
|
||||
color: #222;
|
||||
|
||||
&::placeholder {
|
||||
color: #bababa;
|
||||
}
|
||||
|
||||
&--btn {
|
||||
background-color: #3489e4;
|
||||
border: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-value {
|
||||
color: #1f1f1f;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 26.76px;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Calculator = (props: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const inputValue = useInput('');
|
||||
const serviceInput = useInput('avia');
|
||||
|
||||
let result: string | number = 0;
|
||||
|
||||
if (serviceInput.value === 'avia' && !Number.isNaN(Number(inputValue.value))) {
|
||||
result = (Number(inputValue.value) * 12).toFixed(2);
|
||||
}
|
||||
|
||||
if (serviceInput.value === 'truck' && !Number.isNaN(Number(inputValue.value))) {
|
||||
result = (Number(inputValue.value) * 9).toFixed(2);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledBox>
|
||||
<Typography className='calc-title' id='modal-modal-title'>
|
||||
{t('calc_obj.title')}
|
||||
</Typography>
|
||||
<Typography className='calc-subtitle' id='modal-modal-description'>
|
||||
{t('calc_obj.subtitle')}
|
||||
</Typography>
|
||||
|
||||
<Box className='checkboxes-grid-wrapper'>
|
||||
<Grid container className='checkboxes-grid' justifyContent={'space-between'} spacing={'30px'}>
|
||||
<Grid item xs={4} md={4}>
|
||||
<label className='checkboxes-grid-label'>
|
||||
<img src='/static/images/service1.png' alt='' />
|
||||
<input
|
||||
className='visually-hidden'
|
||||
type='radio'
|
||||
checked={serviceInput.value === 'avia'}
|
||||
onChange={serviceInput.onChange}
|
||||
name='serviceType'
|
||||
value={'avia'}
|
||||
/>
|
||||
</label>
|
||||
</Grid>
|
||||
<Grid item xs={4} md={4}>
|
||||
<label className='checkboxes-grid-label'>
|
||||
<img src='/static/images/service2.png' alt='' />
|
||||
<input
|
||||
className='visually-hidden'
|
||||
type='radio'
|
||||
checked={serviceInput.value === 'truck'}
|
||||
onChange={serviceInput.onChange}
|
||||
name='serviceType'
|
||||
value={'truck'}
|
||||
/>
|
||||
</label>
|
||||
</Grid>
|
||||
{/* <Grid item xs={4} md={4}>
|
||||
<label className='checkboxes-grid-label'>
|
||||
<img src='/static/images/service3.png' alt='' />
|
||||
<input className='visually-hidden' type='radio' name='serviceType' value={'service3'} />
|
||||
</label>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={1.5} mb={3}>
|
||||
{/* <Grid item xs={12} md={6}>
|
||||
<input type='number' placeholder={t('calc_obj.colvo_sht')} className='calc-input' />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<input type='number' placeholder={t('calc_obj.volume')} className='calc-input' />
|
||||
</Grid> */}
|
||||
<Grid item xs={12} md={12}>
|
||||
<input
|
||||
value={inputValue.value}
|
||||
onChange={inputValue.onChange}
|
||||
type='number'
|
||||
placeholder={t('calc_obj.weight_of_product')}
|
||||
className='calc-input'
|
||||
/>
|
||||
</Grid>
|
||||
{/* <Grid item xs={12} md={6}>
|
||||
<input type='submit' value={t('calc_obj.calc_value')} className='calc-input calc-input--btn' />
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
|
||||
<Stack direction={'row'} justifyContent={'center'} alignItems={'center'}>
|
||||
<Typography className='price-value'>
|
||||
{t('price')} = {result}$
|
||||
</Typography>
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
type CalculatorModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const CalculatorModal = ({ open, onClose }: CalculatorModalProps) => {
|
||||
return (
|
||||
<BaseModal maxWidth='400px' onClose={onClose} open={open}>
|
||||
<Calculator />
|
||||
</BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Calculator;
|
||||
export { CalculatorModal };
|
||||
1
src/components/common/Calculator/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import { default as Calculator, CalculatorModal } from './Calculator';
|
||||