Compare commits

..

6 Commits
main ... map

@ -10,8 +10,6 @@
# Production
- add domain to *Caddyfile*
- generate new database password and put it into appropriate places in *docker/docker-compose.yml*
- generate new `SECRET_KEY` with `openssl rand -base64 32` and put it into *docker/docker-compose.yml* geodata: and frontend: environment variables
# Run
- `docker-compose --file docker/docker-compose.yml up --build -d`

@ -1,5 +1,3 @@
import os
from base64 import b64decode
from datetime import datetime, timedelta
from re import IGNORECASE, sub as substitute
@ -18,7 +16,7 @@ from .database import SessionLocal, engine
# Security
# take it from env
SECRET_KEY = b64decode(os.environ["SECRET_KEY"])
SECRET_KEY = b64decode("iYg7wB+sPihtjz50iJTsD0XmOeUwKy2TJtfNLcqFRM8=").hex()
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 3600
@ -201,15 +199,7 @@ def create_items(
for row in sheet["data"]
]
# dump all the data into database
try:
accepted, processed = crud.insert_items(db=db, items=spreadsheet_item_list)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="{}".format(
e
),
)
accepted, processed = crud.insert_items(db=db, items=spreadsheet_item_list)
# transform spreadsheet coordinates data into spacial
crud.add_spacial_data(db=db)

@ -5,7 +5,6 @@ from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
@ -46,7 +45,6 @@ def run_migrations_offline() -> None:
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
)
with context.begin_transaction():
@ -67,11 +65,7 @@ def run_migrations_online() -> None:
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
)
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()

@ -1,38 +0,0 @@
"""Cast resolution as float
Revision ID: 15b3c7a8e804
Revises: 4968a333a100
Create Date: 2022-10-31 23:55:04.806055
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "15b3c7a8e804"
down_revision = "4968a333a100"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands MANUALLY composed for Alembic ###
op.execute(
sa.text(
"ALTER TABLE geodata ALTER COLUMN resolution TYPE FLOAT USING resolution::double precision"
)
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic ###
op.alter_column(
"geodata",
"resolution",
existing_type=sa.Float(),
type_=sa.VARCHAR(),
existing_nullable=True,
)
# ### end Alembic commands ###

@ -89,25 +89,28 @@ def upgrade() -> None:
[
{"database": "fadr", "spreadsheet": "fadr"},
{"database": "internal_id", "spreadsheet": "Уникальный номер образца"},
{"database": "x_coord", "spreadsheet": "Широта"},
{"database": "y_coord", "spreadsheet": "Долгота"},
{"database": "x_coord", "spreadsheet": "кордината X"},
{"database": "y_coord", "spreadsheet": "координата Y"},
{
"database": "category",
"spreadsheet": "Категория",
"database": "gis_category",
"spreadsheet": """Категория в ГИС
Geology - керн, каменный материал
Soil - почва, грунты, морские осадки
Material - искуственные материалы, минералы""",
},
{
"database": "gis_category",
"spreadsheet": """Категория в ГИС""",
"database": "category",
"spreadsheet": "Категория (Археология, Биология, Минералы)",
},
{"database": "basin", "spreadsheet": "Бассейн"},
{"database": "deposit", "spreadsheet": "Месторождение"},
{"database": "well", "spreadsheet": "№ скважины"},
{"database": "depth", "spreadsheet": "Глубины"},
{"database": "stratum", "spreadsheet": "Свита / пласт"},
{"database": "owner", "spreadsheet": "Владелец"},
{"database": "depth", "spreadsheet": "Глубина, интервал, привязка"},
{"database": "stratum", "spreadsheet": "Свита\пласт"},
{"database": "owner", "spreadsheet": "Хозяин (ФИО)"},
{"database": "org", "spreadsheet": "Организация"},
{"database": "ownercontacts", "spreadsheet": "Контакты"},
{"database": "samplelist", "spreadsheet": "Перечень объектов / образцов"},
{"database": "ownercontacts", "spreadsheet": "Контакты хозяина"},
{"database": "samplelist", "spreadsheet": "перечень объектов/образцов"},
{"database": "description", "spreadsheet": "Описание объекта"},
{"database": "form_dimentions", "spreadsheet": "Форма, размер"},
{"database": "datalist", "spreadsheet": "Перечень данных"},
@ -115,7 +118,7 @@ def upgrade() -> None:
{"database": "date", "spreadsheet": "Дата съёмки"},
{"database": "additional_info", "spreadsheet": "Дополнительная информация"},
{"database": "scanner", "spreadsheet": "Томограф"},
{"database": "comment", "spreadsheet": "Комментарий"},
{"database": "comment", "spreadsheet": "комментарий"},
],
)

@ -9,9 +9,8 @@ from sqlalchemy.dialects.postgresql import TSVECTOR
# if we are run as a module or as a script
# https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time/14132912
import sys
parent_module = sys.modules[".".join(__name__.split(".")[:-1]) or "__main__"]
if __name__ == "__main__" or parent_module.__name__ == "__main__":
parent_module = sys.modules['.'.join(__name__.split('.')[:-1]) or '__main__']
if __name__ == '__main__' or parent_module.__name__ == '__main__':
from database import Base
else:
from .database import Base
@ -46,7 +45,7 @@ class ItemBase(Base):
description = Column(String)
form_dimentions = Column(String)
datalist = Column(String)
resolution = Column(Float)
resolution = Column(String)
date = Column(String)
additional_info = Column(String)
scanner = Column(String)

@ -69,7 +69,7 @@ class ItemBase(BaseModel):
description: Optional[str] = None
form_dimentions: Optional[str] = None
datalist: Optional[str] = None
resolution: Optional[float] = None
resolution: Optional[str] = None
date: Optional[Union[datetime, str]] = None
additional_info: Optional[str] = None
scanner: Optional[str] = None

@ -1,47 +1,36 @@
{
order jwtauth before basicauth
}
:80 {
encode zstd gzip
encode zstd gzip
handle_path /api/v1/* {
rewrite * {path}
reverse_proxy geodata:8000
}
handle_path /api/v1/* {
rewrite * {path}
reverse_proxy geodata:8000
}
redir /openapi.json /api/v1/openapi.json permanent
redir /openapi.json /api/v1/openapi.json permanent
handle_path /martin/* {
rewrite * {path}
reverse_proxy martin:3000
}
@is_admin {
vars {http.auth.user.id} "demo"
}
handle_path /martin/* {
rewrite * {path}
reverse_proxy martin:3000
}
handle_path /pgweb/* {
jwtauth {
sign_key {$SECRET_KEY}
from_cookies user_session
}
rewrite * {path}
reverse_proxy @is_admin pgweb:8081
redir /login/ 401
}
handle_path /pgweb/* {
rewrite * {path}
reverse_proxy pgweb:8081
}
handle_path /static/previews/* {
rewrite * {path}
file_server
}
handle_path /static/previews/* {
rewrite * {path}
file_server
}
# play nice with vue-router
# https://caddy.community/t/caddy-with-vue-router/12352
handle {
# play nice with vue-router
# https://caddy.community/t/caddy-with-vue-router/12352
handle {
root * /usr/share/caddy
try_files {path}.html {path} /index.html
file_server
}
}

@ -1,18 +1,12 @@
# build stage
FROM caddy:2-builder-alpine as xcaddy-stage
RUN xcaddy build --with github.com/ggicci/caddy-jwt
FROM node:12.22.12-bullseye as build-stage
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npx browserslist@latest --update-db
RUN npm run build
# production stage
FROM caddy:2-alpine as production-stage
COPY --from=xcaddy-stage /usr/bin/caddy /usr/bin/caddy
COPY --from=build-stage /app/dist /usr/share/caddy
EXPOSE 80

@ -8,8 +8,6 @@ services:
dockerfile: ../../docker/Dockerfile.backend
volumes:
- tmp_vol:/tmp
environment:
- SECRET_KEY="iYg7wB+sPihtjz50iJTsD0XmOeUwKy2TJtfNLcqFRM8="
postgres:
image: "postgis/postgis:13-3.2"
@ -34,18 +32,17 @@ services:
dockerfile: ../docker/Dockerfile.frontend
ports:
- "80:80"
- "443:443"
volumes:
- caddy_data:/data
- caddy_config:/config
- ../caddy:/etc/caddy
- ../previews:/srv
environment:
- SECRET_KEY="iYg7wB+sPihtjz50iJTsD0XmOeUwKy2TJtfNLcqFRM8="
pgweb:
restart: always
image: sosedoff/pgweb:0.11.12
ports:
- "8081:8081"
environment:
DATABASE_URL: "postgres://geodata:QAKvBKvLe4bS9U@postgres/geodata?sslmode=disable" # TODO: change to real password

@ -11,10 +11,9 @@
"maplibre-gl": "^2.1.9",
"vue": "^3.2.13",
"vue-router": "^4.1.5",
"vuestic-ui": "^1.4.13"
"vuestic-ui": "^1.4.7"
},
"devDependencies": {
"@babel/core": "^7.0.0",
"@vue/cli-service": "~5.0.0",
"nodemon": "^2.0.19",
"vue-cli-plugin-vuestic-ui": "~1.0.8"
@ -39,6 +38,7 @@
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
"integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.1.0",
"@jridgewell/trace-mapping": "^0.3.9"
@ -73,6 +73,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
"integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
"dev": true,
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
@ -103,6 +104,7 @@
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"dev": true,
"peer": true,
"bin": {
"json5": "lib/cli.js"
},
@ -115,6 +117,7 @@
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.10.tgz",
"integrity": "sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/types": "^7.18.10",
"@jridgewell/gen-mapping": "^0.3.2",
@ -129,6 +132,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -161,6 +165,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -170,6 +175,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
@ -183,6 +189,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/types": "^7.18.6"
},
@ -195,6 +202,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/types": "^7.18.6"
},
@ -207,6 +215,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz",
"integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
@ -226,6 +235,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz",
"integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/types": "^7.18.6"
},
@ -238,6 +248,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/types": "^7.18.6"
},
@ -250,6 +261,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -277,6 +289,7 @@
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz",
"integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
@ -378,6 +391,7 @@
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10",
@ -392,6 +406,7 @@
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.10.tgz",
"integrity": "sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.10",
@ -413,6 +428,7 @@
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
@ -442,6 +458,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.0",
"@jridgewell/sourcemap-codec": "^1.4.10"
@ -2333,6 +2350,7 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
"dev": true,
"peer": true,
"dependencies": {
"safe-buffer": "~5.1.1"
}
@ -2341,7 +2359,8 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/cookie": {
"version": "0.4.2",
@ -3568,6 +3587,7 @@
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.9.0"
}
@ -3646,6 +3666,7 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4"
}
@ -4281,6 +4302,7 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true,
"peer": true,
"bin": {
"jsesc": "bin/jsesc"
},
@ -7275,6 +7297,7 @@
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4"
}
@ -7564,9 +7587,9 @@
"dev": true
},
"node_modules/vuestic-ui": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/vuestic-ui/-/vuestic-ui-1.4.13.tgz",
"integrity": "sha512-5f2d3isSaXq7RKWHZvGuMaiJraMugAy8DLnGBF9nLjiCZFT3b6E2WQiXpxvsyA4ZA7ZQsaEChNuaymAHdlv+xA==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/vuestic-ui/-/vuestic-ui-1.4.7.tgz",
"integrity": "sha512-t1gKmaoE3lqDDMUWnHZsJTal8cyHTeG9F/cbB1ggMH+loh6Po5tP7KTDA/93iG7dWtuwzuQhwt3/QE2rcbTkTA==",
"dependencies": {
"cleave.js": "^1.6.0",
"colortranslator": "^1.9.2",
@ -8120,6 +8143,7 @@
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
"integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
"dev": true,
"peer": true,
"requires": {
"@jridgewell/gen-mapping": "^0.1.0",
"@jridgewell/trace-mapping": "^0.3.9"
@ -8145,6 +8169,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
"integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
"dev": true,
"peer": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6",
@ -8167,7 +8192,8 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"dev": true
"dev": true,
"peer": true
}
}
},
@ -8176,6 +8202,7 @@
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.10.tgz",
"integrity": "sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==",
"dev": true,
"peer": true,
"requires": {
"@babel/types": "^7.18.10",
"@jridgewell/gen-mapping": "^0.3.2",
@ -8187,6 +8214,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"peer": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
@ -8211,13 +8239,15 @@
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
"integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
"dev": true
"dev": true,
"peer": true
},
"@babel/helper-function-name": {
"version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz",
"integrity": "sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==",
"dev": true,
"peer": true,
"requires": {
"@babel/template": "^7.18.6",
"@babel/types": "^7.18.9"
@ -8228,6 +8258,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
"integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
"dev": true,
"peer": true,
"requires": {
"@babel/types": "^7.18.6"
}
@ -8237,6 +8268,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
"integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
"dev": true,
"peer": true,
"requires": {
"@babel/types": "^7.18.6"
}
@ -8246,6 +8278,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz",
"integrity": "sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g==",
"dev": true,
"peer": true,
"requires": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
@ -8262,6 +8295,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz",
"integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==",
"dev": true,
"peer": true,
"requires": {
"@babel/types": "^7.18.6"
}
@ -8271,6 +8305,7 @@
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
"integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
"dev": true,
"peer": true,
"requires": {
"@babel/types": "^7.18.6"
}
@ -8279,7 +8314,8 @@
"version": "7.18.10",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz",
"integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==",
"dev": true
"dev": true,
"peer": true
},
"@babel/helper-validator-identifier": {
"version": "7.18.6",
@ -8298,6 +8334,7 @@
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz",
"integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==",
"dev": true,
"peer": true,
"requires": {
"@babel/template": "^7.18.6",
"@babel/traverse": "^7.18.9",
@ -8377,6 +8414,7 @@
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
"dev": true,
"peer": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10",
@ -8388,6 +8426,7 @@
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.10.tgz",
"integrity": "sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==",
"dev": true,
"peer": true,
"requires": {
"@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.18.10",
@ -8406,6 +8445,7 @@
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
"dev": true,
"peer": true,
"requires": {
"@babel/helper-string-parser": "^7.18.10",
"@babel/helper-validator-identifier": "^7.18.6",
@ -8432,6 +8472,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
"integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
"dev": true,
"peer": true,
"requires": {
"@jridgewell/set-array": "^1.0.0",
"@jridgewell/sourcemap-codec": "^1.4.10"
@ -9966,6 +10007,7 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
"integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==",
"dev": true,
"peer": true,
"requires": {
"safe-buffer": "~5.1.1"
},
@ -9974,7 +10016,8 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
"dev": true,
"peer": true
}
}
},
@ -10903,7 +10946,8 @@
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"dev": true
"dev": true,
"peer": true
},
"geojson-vt": {
"version": "3.2.1",
@ -10963,7 +11007,8 @@
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
"dev": true,
"peer": true
},
"globby": {
"version": "11.1.0",
@ -11432,7 +11477,8 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
"dev": true,
"peer": true
},
"json-parse-better-errors": {
"version": "1.0.2",
@ -13658,7 +13704,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true
"dev": true,
"peer": true
},
"to-regex-range": {
"version": "5.0.1",
@ -13892,9 +13939,9 @@
"dev": true
},
"vuestic-ui": {
"version": "1.4.13",
"resolved": "https://registry.npmjs.org/vuestic-ui/-/vuestic-ui-1.4.13.tgz",
"integrity": "sha512-5f2d3isSaXq7RKWHZvGuMaiJraMugAy8DLnGBF9nLjiCZFT3b6E2WQiXpxvsyA4ZA7ZQsaEChNuaymAHdlv+xA==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/vuestic-ui/-/vuestic-ui-1.4.7.tgz",
"integrity": "sha512-t1gKmaoE3lqDDMUWnHZsJTal8cyHTeG9F/cbB1ggMH+loh6Po5tP7KTDA/93iG7dWtuwzuQhwt3/QE2rcbTkTA==",
"requires": {
"cleave.js": "^1.6.0",
"colortranslator": "^1.9.2",

@ -10,12 +10,11 @@
"maplibre-gl": "^2.1.9",
"vue": "^3.2.13",
"vue-router": "^4.1.5",
"vuestic-ui": "^v1.8.6"
"vuestic-ui": "^1.4.7"
},
"devDependencies": {
"@vue/cli-service": "~5.0.0",
"nodemon": "^2.0.19",
"vue-cli-plugin-vuestic-ui": "~1.0.8",
"@babel/core": "^7.0.0"
"vue-cli-plugin-vuestic-ui": "~1.0.8"
}
}
}

@ -2,7 +2,9 @@
<div>
<h1 style="font-size: 3rem; margin: 1rem;">Текущий формат</h1>
<h2 style="margin-left: 1rem;">(скоординированный)</h2>
<div class="va-table-responsive" style="margin: 1rem;">
<va-radio class="radio-switcher" v-for="(option, index) in viewOptions" :key="index" v-model="selectedOption"
:option="option" :label="viewLabels[index]"></va-radio>
<div class="va-table-responsive" v-if="selectedOption === 'table-view'">
<table class="va-table va-table--striped detail-table">
<thead>
<tr>
@ -18,6 +20,24 @@
</tbody>
</table>
</div>
<va-list v-else-if="selectedOption === 'list-view'">
<va-list-item v-for="(_, header, index) in items[0]" :key="index" class="header-list">
<va-list-item-section icon>
<va-icon name="circle" color="gray" size="small"></va-icon>
</va-list-item-section>
<va-list-item-section>
<va-list-item-label class="header-item" :lines="2">{{ header }}</va-list-item-label>
</va-list-item-section>
</va-list-item>
</va-list>
<div v-else class="card-row">
<div class="card-item flex lg4" v-for="(_, header, index) in items[0]" :key="index">
<va-card :bordered="false">
<va-card-content>{{ header }}</va-card-content>
</va-card>
</div>
</div>
</div>
</template>
@ -28,7 +48,18 @@ export default defineComponent({
name: "active-schema-screen",
data() {
return {
columns: []
columns: [],
viewOptions: [
"table-view",
"list-view",
"mosaic-view",
],
viewLabels: [
"Табличный вид",
"Списочный вид",
"Мозаичный вид",
],
selectedOption: "table-view"
}
},
methods: {
@ -58,9 +89,20 @@ export default defineComponent({
<style lang="css" scoped>
.radio-switcher {
margin: 1rem;
}
.header-item {
padding: 0.5rem;
}
.card-row {
display: flex;
flex-wrap: wrap;
}
.card-item {
margin: 1rem;
}
</style>

@ -1,41 +1,35 @@
<template>
<div class="filter_panel">
<div class="sidebar-item">
<h3>Проект / тема</h3>
<project-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Категория</h3>
<category-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Описание</h3>
<description-filter @filter="$emit('filter', $event)" />
<h3>ГИС категория</h3>
<gis-category-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Свита / пласт</h3>
<stratum-filter @filter="$emit('filter', $event)" />
<h3>Томограф</h3>
<scanner-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Размер образца</h3>
<form-dimensions-filter @filter="$emit('filter', $event)" />
<h3>Организация</h3>
<org-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Перечень данных</h3>
<datalist-filter @filter="$emit('filter', $event)" />
<h3>Бассейн</h3>
<basin-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Разрешение съемки</h3>
<resolution-filter @filter="$emit('filter', $event)" />
<h3>Свита / пласт</h3>
<stratum-filter @filter="$emit('filter', $event)" />
</div>
<div class="sidebar-item">
<h3>Глубины</h3>
<depth-filter @filter="$emit('filter', $event)" />
@ -53,27 +47,9 @@ import OrgFilter from "@/components/filters/OrgFilter.vue";
import StratumFilter from "@/components/filters/StratumFilter.vue";
import DepthFilter from "@/components/filters/DepthFilter.vue";
import BasinFilter from "@/components/filters/BasinFilter.vue";
import ProjectFilter from "@/components/filters/ProjectFilter.vue";
import DescriptionFilter from "@/components/filters/DescriptionFilter.vue";
import DatalistFilter from "@/components/filters/DatalistFilter.vue";
import ResolutionFilter from "@/components/filters/ResolutionFilter.vue";
import FormDimensionsFilter from "@/components/filters/FormDimensionsFilter.vue";
export default {
name: "filter-panel",
components: {
CategoryFilter,
GisCategoryFilter,
ScannerFilter,
OrgFilter,
StratumFilter,
DepthFilter,
BasinFilter,
ProjectFilter,
DescriptionFilter,
DatalistFilter,
ResolutionFilter,
FormDimensionsFilter
},
components: { CategoryFilter, GisCategoryFilter, ScannerFilter, OrgFilter, StratumFilter, DepthFilter, BasinFilter },
methods: {},
data() {
return {}
@ -97,7 +73,7 @@ export default {
flex-direction: column;
}
.sidebar-item>h3 {
.sidebar-item > h3 {
margin-bottom: 0.4rem
}

@ -38,17 +38,9 @@
<va-card id="map" class="content-container map-container">
<item-map-component v-if="detailsLoaded" :internal_id="this.itemDetails.internal_id"
:x_coord="this.xComputed" :y_coord="this.yComputed" />
:x_coord="this.itemDetails.x_coord" :y_coord="this.itemDetails.y_coord" />
<h3 v-else>Карта загружается</h3>
</va-card>
<va-card id="similar" class="content-container" v-if="detailsLoaded">
<va-scroll-container vertical>
<va-card-title>Похожие образцы</va-card-title>
<va-data-table :items="filteredSimilarItems" :columns="filteredColumns" :hoverable="true"
:clickable="true" select-mode="single" @row:click="(e) => handleClick(e)"
class="similar-items-table" />
</va-scroll-container>
</va-card>
<va-card id="contact" class="content-container">
<va-card-title>Связь с представителем</va-card-title>
</va-card>
@ -63,27 +55,21 @@ export default {
data() {
return {
itemDetails: {},
shownDetails: [
hidden: [
"fadr",
"internal_id",
"category",
"basin",
"stratum",
"form_dimentions",
"resolution",
"scanner",
"datalist",
"comment"
"x_coord",
"y_coord",
"owner",
"ownercontacts",
"id",
"depth_min",
"depth_max",
],
imageExtension: "bmp",
hasImage: false,
detailsLoaded: false,
columnHeaders: [],
showColumns: [
"description",
"deposit",
"category",
],
similarItems: [],
columnHeaders: []
};
},
methods: {
@ -91,6 +77,7 @@ export default {
fetch(`/api/v1/item/${this.$route.params.id}`)
.then(res => res.json())
.then(data => this.itemDetails = data)
.then(() => this.detailsLoaded = true)
.catch((e) => console.log(e));
},
async fetchColumnHeaders() {
@ -118,39 +105,11 @@ export default {
}
return `/static/previews/${this.itemDetails.fadr}+/${this.itemDetails.internal_id}.${this.imageExtension}`
},
async fetchSimilarItems() {
fetch(`/api/v1/detailed_search/`,
{ method: 'POST', body: JSON.stringify({ fadr: this.itemDetails.fadr }), headers: { 'Content-Type': 'application/json' } })
.then(res => res.json())
.then(data => this.similarItems = data)
.then(() => this.detailsLoaded = true)
.catch((e) => console.log(e));
},
handleClick(e) {
if (e.event.ctrlKey) {
let routeData = this.$router.resolve({ name: 'item-details' });
window.open(routeData.href, '_blank');
} else {
this.detailsLoaded = false;
this.$router.replace(`/items/${e.item.id}`).then(a => {
this.fetchItemDetails();
});
}
},
}
},
mounted() {
created() {
this.fetchItemDetails();
this.fetchColumnHeaders();
this.fetchItemDetails().then(
this.fetchSimilarItems()
);
},
watch: {
'itemDetails': function (_oldval, _newval) {
this.fetchSimilarItems()
}
},
computed: {
slotPreviewPath() {
@ -166,40 +125,9 @@ export default {
},
formattedDetails() {
return Object.entries(this.itemDetails)
.filter(([k, _v]) => (this.shownDetails.includes(k)))
.filter(([k, _v]) => !(this.hidden.includes(k)))
.map(([k, v]) => [this.dictHeaders[k], v])
},
filteredColumns() {
return this.columnHeaders
.filter(header => this.showColumns.includes(header.database))
.sort((header1, header2) => {
return this.showColumns.indexOf(header1.database) - this.showColumns.indexOf(header2.database)
})
.map(header => {
return {
key: header.database,
label: header.spreadsheet,
sortable: true,
}
})
},
forceSimilarItemsReload() {
// https://stackoverflow.com/questions/48641295/async-computed-in-components-vuejs
this.fetchSimilarItems();
for (let _i in this.itemDetails) {
return this.itemDetails.fadr;
}
return false;
},
filteredSimilarItems() {
return [...this.similarItems].filter(item => item.internal_id !== this.itemDetails.internal_id)
},
xComputed() {
return this.itemDetails.x_coord ? this.itemDetails.x_coord : 55.75
},
yComputed() {
return this.itemDetails.y_coord ? this.itemDetails.y_coord : 37.62
},
},
components: { ItemMapComponent }
}
@ -218,11 +146,6 @@ export default {
margin: 1rem auto;
}
.similar-items-table {
width: 45rem;
overflow-x: hidden;
}
.item-description {
margin-top: 2rem;
margin-bottom: 2rem;

@ -7,7 +7,7 @@
<script>
import maplibregl from "maplibre-gl";
import { markRaw, onMounted, onUnmounted, reactive, shallowRef, watchEffect, } from "vue";
import { markRaw, onMounted, onUnmounted, reactive, shallowRef, } from "vue";
export default {
name: "item-map-component",
props: {
@ -25,31 +25,6 @@ export default {
setup(props, _context) {
const mapContainer = shallowRef(null);
const map = shallowRef(null);
let isSampleLayerAdded = false;
const addSamplesLayer = () => {
map.value.addSource('samples', {
'type': 'vector',
"tiles": ["http://localhost/martin/public.geodata/{z}/{x}/{y}.pbf"],
'promoteId': 'fadr',
});
map.value.addLayer({
'id': 'samples-layer',
'source': 'samples',
'source-layer': 'public.geodata',
'type': 'circle',
'paint': {
'circle-stroke-width': 1,
'circle-stroke-color': '#FFFFFF',
'circle-color': '#d95f0e',
'circle-opacity': 0.8,
'circle-radius': 16
},
filter: ["==", ["get", "internal_id"], props.internal_id]
});
}
onMounted(() => {
const apiKey = "pk.eyJ1IjoiZ2hlcm1hbnQiLCJhIjoiY2pncDUwcnRmNDQ4ZjJ4czdjZXMzaHZpNyJ9.3rFyYRRtvLUngHm027HZ7A";
@ -83,11 +58,25 @@ export default {
}
);
watchEffect(() => {
if (!isSampleLayerAdded && typeof props.internal_id !== typeof undefined) {
isSampleLayerAdded = true;
addSamplesLayer();
}
map.value.addSource('samples', {
'type': 'vector',
"tiles": ["http://localhost:8080/martin/public.geodata/{z}/{x}/{y}.pbf"],
'promoteId': 'fadr',
});
map.value.addLayer({
'id': 'samples-layer',
'source': 'samples',
'source-layer': 'public.geodata',
'type': 'circle',
'paint': {
'circle-stroke-width': 1,
'circle-stroke-color': '#FFFFFF',
'circle-color': '#d95f0e',
'circle-opacity': 0.8,
'circle-radius': 16
},
filter: ["==", ["get", "internal_id"], props.internal_id]
});
});

@ -41,7 +41,6 @@ export default {
.then(data => {
if (data.access_token) {
localStorage.setItem('user', JSON.stringify(data));
document.cookie = `user_session=${JSON.stringify(data.access_token)}; path=/; max-age=${60 * 60 * 24 * 365}; SameSite=strict`
this.$router.push('/admin')
}
})

@ -1,304 +1,303 @@
<template>
<div class="map-wrap">
<div id="layer-control" class="d-flex flex-direction-column">
<va-radio class="radio-switcher" v-for="(option, index) in viewOptions" :key="index"
v-model="selectedOption" :option="option" :label="viewLabels[index]"></va-radio>
<va-checkbox class="mb-4 fields-switch" v-model="valuecb" label="Месторождения" />
</div>
<div id="map" class="map" ref="mapContainer"></div>
<div class="map-wrap">
<div id="layer-control" class="d-flex flex-direction-column">
<va-radio
v-for="(option, index) in options"
:key="index"
v-model="selectedOption"
:option="option"
:label="labels[index]"
/>
<va-checkbox class="mb-4" v-model="valuecb" :label="label" />
</div>
<div id="map" class="map" ref="mapContainer"></div>
</div>
</template>
<script>
import maplibregl from "maplibre-gl";
import { markRaw, onMounted, onUnmounted, reactive, ref, shallowRef, watch } from "vue";
import {
markRaw,
onMounted,
onUnmounted,
reactive,
shallowRef,
watch,
} from "vue";
export default {
name: "map-component",
props: {
idlist: {
type: Array,
required: true,
}
name: "map-component",
props: {
idlist: {
type: Array,
required: true,
},
setup(props, context) {
const mapContainer = shallowRef(null);
const map = shallowRef(null);
let currentFadr = reactive({ "fadr": [] });
const valuecb = ref(true);
const selectedOption = ref("topo");
const viewOptions = ["topo", "satellite", "mono"];
const viewLabels = ["Карта", "Спутник", "Монохром"];
const apiKey = "pk.eyJ1IjoiZ2hlcm1hbnQiLCJhIjoiY2pncDUwcnRmNDQ4ZjJ4czdjZXMzaHZpNyJ9.3rFyYRRtvLUngHm027HZ7A";
},
data() {
return {
valuebm: "",
options: ["Карта", "Спутник", "Монохром"],
valuecb: true,
label: "Месторождения",
};
},
setup(props, context) {
const mapContainer = shallowRef(null);
const map = shallowRef(null);
let currentFadr = reactive({ fadr: [] });
const apiKey =
"pk.eyJ1IjoiZ2hlcm1hbnQiLCJhIjoiY2pncDUwcnRmNDQ4ZjJ4czdjZXMzaHZpNyJ9.3rFyYRRtvLUngHm027HZ7A";
watch(
() => [...props.idlist],
(_currentValue, _oldValue) => {
updateSamplesLayer();
}
);
const buildMap = () => {
map.value = markRaw(
new maplibregl.Map({
container: mapContainer.value, // container id
// DOCS: https://maplibre.org/maplibre-gl-js-docs/style-spec/
style: {
version: 8,
sources: {},
layers: [],
},
center: [80, 40], // starting position [lng, lat]
zoom: 2, // starting zoom
maxZoom: 10,
})
);
map.value.on("load", () => {
const basemapTiles = {
"topo": [`https://api.mapbox.com/styles/v1/ghermant/cl9vd1nji002n15s2xxj25f4m/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`],
"satellite": [`https://api.mapbox.com/styles/v1/ghermant/cl9olarp0002i14vq9a2d0e7g/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`],
"mono": [`https://api.mapbox.com/styles/v1/ghermant/cl9vd7xwi004u14nxu3j83scj/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`]
};
"topo": [`https://api.mapbox.com/styles/v1/ghermant/cl9vd1nji002n15s2xxj25f4m/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`],
"satellite": [`https://api.mapbox.com/styles/v1/ghermant/cl9vd1nji002n15s2xxj25f4m/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`],
"mono": [`https://api.mapbox.com/styles/v1/ghermant/cl9olarp0002i14vq9a2d0e7g/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`]
}
watch(() => [...props.idlist], (_currentValue, _oldValue) => {
updateSamplesLayer()
map.value.addSource("basemap", {
type: "raster",
tiles: [
`https://api.mapbox.com/styles/v1/ghermant/cl9vd1nji002n15s2xxj25f4m/tiles/256/{z}/{x}/{y}@2x?access_token=${apiKey}`,
],
});
watch(() => valuecb.value, (_currentValue, _oldValue) => {
updateFieldsLayer()
map.value.addLayer({
id: "basemap-layer",
type: "raster",
source: "topo-basemap",
paint: {},
});
watch(() => selectedOption.value, (_currentValue, _oldValue) => {
updateBaseLayer()
})
const buildMap = () => {
map.value = markRaw(new maplibregl.Map({
container: mapContainer.value, // container id
// DOCS: https://maplibre.org/maplibre-gl-js-docs/style-spec/
style: {
version: 8,
sources: {},
layers: []
},
center: [80, 40], // starting position [lng, lat]
zoom: 2, // starting zoom
maxZoom: 10
}));
map.value.on('load', () => {
map.value.addSource('basemap', {
'type': 'raster',
'tiles': basemapTiles[selectedOption.value]
})
map.value.addLayer(
{
'id': 'basemap-layer',
'type': 'raster',
'source': 'basemap',
'paint': {}
}
);
map.value.addSource("fields", {
type: "vector",
tiles: [`https://api.mapbox.com/v4/ghermant.cpc0xaaw/{z}/{x}/{y}.mvt?access_token=${apiKey}`]
})
map.value.addLayer({
'id': 'fields-layer',
'type': 'fill',
'source': 'fields',
'source-layer': 'mygeomapZoom2-2utkfs',
'paint': {
'fill-color': '#fc9272',
'fill-opacity': 0.5
},
'layout': {
'visibility': 'visible'
}
})
map.value.addSource('samples', {
'type': 'vector',
"tiles": ["http://localhost/martin/public.geodata/{z}/{x}/{y}.pbf"],
'promoteId': 'fadr',
});
map.value.addLayer({
'id': 'samples-layer',
'source': 'samples',
'source-layer': 'public.geodata',
'type': 'circle',
'paint': {
'circle-stroke-width': 1,
'circle-stroke-color': '#FFFFFF',
'circle-color': [
'case',
['boolean', ['feature-state', 'beenClicked'], false],
'#fec44f',
'#d95f0e'
],
'circle-opacity': 0.8,
'circle-radius': 8
},
filter: ["match", ["get", "internal_id"], props.idlist, true, false]
});
});
const popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
});
map.value.on("click", "fields-layer", (e) => {
new maplibregl.Popup({})
.setLngLat(e.lngLat)
.setHTML(e.features[0].properties["descriptio"])
.addTo(map.value);
});
map.value.on("mouseover", "fields-layer", (e) => {
map.value.getCanvas().style.cursor = "pointer";
});
map.value.on("mouseleave", "fields-layer", (e) => {
map.value.getCanvas().style.cursor = "";
});
map.value.on('mouseover', 'samples-layer', (e) => {
// Change the cursor style as a UI indicator.
map.value.getCanvas().style.cursor = 'pointer';
// Populate the popup and set its coordinates
// based on the feature found.
popup
.setLngLat(e.features[0].geometry.coordinates)
.setHTML(`Записей в точке: <b>${e.features.length}</b>`)
.addTo(map.value);
});
map.value.on('mouseleave', 'samples-layer', (e) => {
map.value.getCanvas().style.cursor = '';
popup.remove();
});
map.value.on('click', 'samples-layer', (e) => {
let newFadr = e.features[0].properties["fadr"];
let newFadrFound = currentFadr["fadr"].indexOf(newFadr)
if (newFadrFound > -1) {
//remove old paint
map.value.setFeatureState(
{
source: 'samples',
sourceLayer: 'public.geodata',
id: newFadr
},
{ beenClicked: false }
);
// remove from filters
currentFadr["fadr"].splice(newFadrFound, 1)
} else {
// apply new paint
map.value.setFeatureState(
{
source: 'samples',
sourceLayer: 'public.geodata',
id: newFadr
},
{ beenClicked: true }
);
// add to filters
currentFadr["fadr"].push(newFadr);
}
context.emit('mapClick', currentFadr);
});
map.value.on('click', (e) => {
const features = map.value.queryRenderedFeatures(e.point)
if (!features.length) {
currentFadr["fadr"].forEach((oldFadr) => {
map.value.setFeatureState(
{
source: 'samples',
sourceLayer: 'public.geodata',
id: oldFadr
},
{ beenClicked: false }
);
})
currentFadr["fadr"].length = 0;
}
context.emit('mapClick', currentFadr);
});
};
const updateSamplesLayer = () => {
if (props.idlist.length < 2000) {
map.value.setFilter('samples-layer', ["in", "internal_id", ...props.idlist])
} else {
map.value.setFilter('samples-layer', null)
}
};
const updateFieldsLayer = () => {
const visibility = valuecb.value ? 'visible' : 'none';
map.value.setLayoutProperty('fields-layer', 'visibility', visibility);
};
const updateBaseLayer = () => {
const selectedBasemap = selectedOption.value;
// https://stackoverflow.com/questions/38631344/recommended-way-to-switch-tile-urls-in-mapbox-gl-js
// Set the tile url to a cache-busting url (to circumvent browser caching behaviour):
map.value.getSource('basemap').tiles = basemapTiles[selectedBasemap]
// Remove the tiles for a particular source
map.value.style.sourceCaches['basemap'].clearTiles()
// Load the new tiles for the current viewport (map.value.transform -> viewport)
map.value.style.sourceCaches['basemap'].update(map.value.transform)
// Force a repaint, so that the map will be repainted without you having to touch the map
map.value.triggerRepaint()
}
onMounted(() => {
buildMap()
map.value.addSource("fields", {
type: "vector",
tiles: [
`https://api.mapbox.com/v4/ghermant.cpc0xaaw/{z}/{x}/{y}.mvt?access_token=${apiKey}`,
],
});
map.value.addLayer({
id: "fields-layer",
type: "fill",
source: "fields",
"source-layer": "mygeomapZoom2-2utkfs",
paint: {
"fill-color": "#fc9272",
"fill-opacity": 0.5,
},
layout: {
visibility: "visible",
},
minzoom: 4
});
onUnmounted(() => {
// TODO: remove even listeners too
map.value?.remove();
})
map.value.addSource("samples", {
type: "vector",
tiles: [
"http://localhost:8080/martin/public.geodata/{z}/{x}/{y}.pbf",
],
promoteId: "fadr",
});
map.value.addLayer({
id: "samples-layer",
source: "samples",
"source-layer": "public.geodata",
type: "circle",
paint: {
"circle-stroke-width": 1,
"circle-stroke-color": "#FFFFFF",
"circle-color": [
"case",
["boolean", ["feature-state", "beenClicked"], false],
"#fec44f",
"#d95f0e",
],
"circle-opacity": 0.8,
"circle-radius": 8,
},
filter: ["match", ["get", "internal_id"], props.idlist, true, false],
});
});
const popup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
})
map.value.on("click", "fields-layer", (e) => {
new maplibregl.Popup({})
.setLngLat(e.lngLat)
.setHTML(e.features[0].properties["descriptio"])
.addTo(map.value);
})
map.value.on("mouseover", "fields-layer", (e) => {
map.value.getCanvas().style.cursor = "pointer";
})
map.value.on("mouseleave", "fields-layer", (e) => {
map.value.getCanvas().style.cursor = "";
})
map.value.on("mouseover", "samples-layer", (e) => {
// Change the cursor style as a UI indicator.
map.value.getCanvas().style.cursor = "pointer";
// Populate the popup and set its coordinates
// based on the feature found.
popup
.setLngLat(e.lngLat)
.setHTML(`Записей в точке: <b>${e.features.length}</b>`)
.addTo(map.value);
});
map.value.on("mouseleave", "samples-layer", (e) => {
map.value.getCanvas().style.cursor = "";
popup.remove();
});
map.value.on("click", "samples-layer", (e) => {
let newFadr = e.features[0].properties["fadr"];
let newFadrFound = currentFadr["fadr"].indexOf(newFadr);
if (newFadrFound > -1) {
//remove old paint
map.value.setFeatureState(
{
source: "samples",
sourceLayer: "public.geodata",
id: newFadr,
},
{ beenClicked: false }
);
// remove from filters
currentFadr["fadr"].splice(newFadrFound, 1);
} else {
// apply new paint
map.value.setFeatureState(
{
source: "samples",
sourceLayer: "public.geodata",
id: newFadr,
},
{ beenClicked: true }
);
// add to filters
currentFadr["fadr"].push(newFadr);
}
return {
map, mapContainer, currentFadr, valuecb, selectedOption, viewOptions, viewLabels
context.emit("mapClick", currentFadr);
});
map.value.on("click", (e) => {
const features = map.value.queryRenderedFeatures(e.point);
if (!features.length) {
currentFadr["fadr"].forEach((oldFadr) => {
map.value.setFeatureState(
{
source: "samples",
sourceLayer: "public.geodata",
id: oldFadr,
},
{ beenClicked: false }
);
});
currentFadr["fadr"].length = 0;
}
}
}
context.emit("mapClick", currentFadr);
});
};
const updateSamplesLayer = () => {
if (map.value.getLayer("samples-layer")) {
map.value.removeLayer("samples-layer");
}
if (props.idlist.length) {
map.value.addLayer({
id: "samples-layer",
source: "samples",
"source-layer": "public.geodata",
type: "circle",
paint: {
"circle-stroke-width": 1,
"circle-stroke-color": "#FFFFFF",
"circle-color": [
"case",
["boolean", ["feature-state", "beenClicked"], false],
"#fec44f",
"#d95f0e",
],
"circle-opacity": 0.8,
"circle-radius": 8,
},
filter: ["match", ["get", "internal_id"], props.idlist, true, false],
});
}
};
onMounted(() => {
buildMap();
});
onUnmounted(() => {
// TODO: remove even listeners too
map.value?.remove();
});
return {
map,
mapContainer,
currentFadr,
};
},
};
</script>
<style lang="css">
.map-wrap {
position: relative;
height: 100vh;
width: 100%;
position: relative;
height: 100vh;
width: 100%;
}
.map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
/* z-index: -1; */
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -1;
}
#layer-control {
position: absolute;
top: 1rem;
right: 1rem;
z-index: 5;
flex-direction: column;
}
.radio-switcher {
padding-left: 0.3rem;
}
.fields-switch {
margin-top: 1rem;
}
.va-radio__text, .va-checkbox__label {
color: white;
position: absolute;
top: 1rem;
right: 1rem;
}
</style>

@ -3,56 +3,16 @@
<va-navbar shape color="#79589f" class="navbar">
<template #left>
<va-navbar-item class="top-icon">
<va-popover message="Карта" prevent-overflow stick-to-edges>
<va-icon size="3rem" name="public" @click="$router.push('/')" />
<va-popover message="Главная страница">
<va-icon size="3rem" color="gray" name="public" @click="$router.push('/')" />
</va-popover>
</va-navbar-item>
<slot></slot>
</template>
<template #right>
<va-dropdown :close-on-content-click="false" placement="bottom-end" prevent-overflow>
<template #anchor>
<va-navbar-item class="top-icon">
<va-popover message="Корзина" prevent-overflow stick-to-edges>
<va-icon size="3rem" name="shopping_basket" @click="" />
</va-popover>
</va-navbar-item>
</template>
<va-dropdown-content>
<va-card class="flex">
<va-card-title>Корзина</va-card-title>
<va-card-content v-if="!itemsInCart.length" class="cart-inner cart-empty">
<p class="cart-empty__text">Корзина пуста</p>
<p class="cart-empty__text">Попробуйте перейти на страницу образца</p>
<p v-if="isItemPath" class="cart-empty__text">
и <va-button @click="addToCart">Добавить в корзину</va-button>
</p>
</va-card-content>
<template v-else>
<va-card-actions align="between">
<template v-if="isItemPath">
<va-button v-if="!isPathInCart" @click="addToCart">Добавить в корзину
</va-button>
<va-button v-else @click="removeFromCart">Убрать из корзины</va-button>
</template>
<va-button @click="copyCart">{{ copyCartText }}</va-button>
<va-button color="warning" @click="emptyCart">Очистить корзину</va-button>
</va-card-actions>
<va-card-content class="cart-inner">
<va-data-table :items="itemsInCart" :columns="filteredColumns" :hoverable="true"
:clickable="true" select-mode="single" @row:click="(e) => handleClick(e)"
class="similar-items-table" />
</va-card-content>
</template>
</va-card>
</va-dropdown-content>
</va-dropdown>
<va-navbar-item class="top-icon">
<va-popover message="Аккаунт" prevent-overflow stick-to-edges>
<va-icon size="3rem" name="account_box" @click="$emit('accountClick')" />
<va-popover message="Аккаунт">
<va-icon size="3rem" color="gray" name="account_box" @click="$emit('accountClick')" />
</va-popover>
</va-navbar-item>
</template>
@ -64,98 +24,6 @@
<script>
export default {
name: "navbar",
data() {
return {
columnHeaders: [],
showColumns: [
"internal_id",
"description",
],
itemsInCart: [],
cartListCopied: false,
}
},
methods: {
addToCart() {
if (!this.isPathInCart) {
fetch(`/api/v1/item/${this.$route.params.id}`)
.then(res => res.json())
.then(data => {
this.itemsInCart.push(data);
localStorage.setItem("cart", JSON.stringify(this.itemsInCart));
this.cartListCopied = false;
})
.catch((e) => console.log(e));
}
},
removeFromCart() {
const routeId = Number.parseInt(this.$route.params.id);
this.itemsInCart = this.itemsInCart.filter(item => item.id !== routeId)
localStorage.setItem("cart", JSON.stringify(this.itemsInCart));
this.cartListCopied = false;
},
copyCart() {
const textToCopy = this.itemsInCart
.map(item => item.internal_id)
.reduce((list, item_id) => list + "," + item_id)
navigator.clipboard.writeText(textToCopy)
.then(() => this.cartListCopied = true)
.catch(e => console.log("Error copying item list ("
+ textToCopy
+ ") from cart: "
+ e))
},
emptyCart() {
this.itemsInCart = [];
localStorage.removeItem("cart");
},
restoreCart() {
if (localStorage.getItem("cart")) {
this.itemsInCart = JSON.parse(localStorage.getItem("cart"))
}
},
async fetchColumnHeaders() {
fetch(`/api/v1/headers/`)
.then(res => res.json())
.then(data => this.columnHeaders = data)
.catch((e) => console.log(e));
},
handleClick(e) {
if (String(e.item.id) !== this.$route.params.id) {
window.open(`/items/${e.item.id}`, '_blank')
}
}
},
mounted() {
this.fetchColumnHeaders();
this.restoreCart()
},
computed: {
isItemPath() {
return this.$route.name === "item-details";
},
filteredColumns() {
return this.columnHeaders
.filter(header => this.showColumns.includes(header.database))
.sort((header1, header2) => {
return this.showColumns.indexOf(header1.database) - this.showColumns.indexOf(header2.database)
})
.map(header => {
return {
key: header.database,
label: header.spreadsheet,
sortable: true,
}
})
},
isPathInCart() {
const routeId = Number.parseInt(this.$route.params.id);
return this.itemsInCart.some(item => item.id === routeId)
},
copyCartText() {
return (this.cartListCopied ? 'Скопировано ✓✓' : 'Скопировать список образцов')
}
},
}
</script>
@ -164,19 +32,4 @@ export default {
.navbar {
position: relative;
}
.cart-inner {
display: flex;
flex-direction: column;
min-width: 30rem;
min-height: 30rem;
}
.cart-empty {
justify-content: center;
}
.cart-empty__text {
margin: 1rem auto;
}
</style>

@ -5,12 +5,11 @@
@mapClick="$emit('mapClick', $event)" />
</va-card>
<va-card class="content-container">
<va-scroll-container vertical>
<va-data-table virtual-scroller sticky-header v-if="items.length" :items="items" :columns="columns" :hoverable="true" :clickable="true"
@row:click="(e) => $router.push(`/items/${e.item.id}`)"
no-data-filtered-html="Не найдено образцов, соответствующих данному запросу" />
<h1 v-else class="no-items">Не найдено образцов, соответствующих данному запросу</h1>
</va-scroll-container>
<va-data-table v-if="items.length" :items="items" :columns="columns" :hoverable="true" :clickable="true"
:per-page="perPage" :current-page="currentPage" @row:click="(e) => $router.push(`/items/${e.item.id}`)"
no-data-filtered-html="Не найдено образцов, соответствующих данному запросу" />
<va-pagination v-if="items.length" v-model="currentPage" :pages="pages" input style="margin: 0.5rem auto" />
<h1 v-else class="no-items">Не найдено образцов, соответствующих данному запросу</h1>
</va-card>
</div>
</template>
@ -33,12 +32,23 @@ export default {
},
data() {
return {
perPage: 10,
currentPage: 1,
settleFinished: false,
}
},
methods: {},
computed: {
pages() {
this.settleFinished = true;
this.currentPage = 1;
return (this.perPage && this.perPage !== 0)
? Math.ceil(this.items.length / this.perPage)
: this.items.length
},
itemIdList() {
return this.items.map(item => item.internal_id)
},

@ -1,6 +1,6 @@
<template>
<div>
<va-sidebar class="sidebar-proper" width="18rem">
<va-sidebar color="white" text-color="black" class="sidebar-proper" width="18rem">
<slot></slot>
</va-sidebar>
</div>
@ -17,7 +17,7 @@ export default {
<style lang="css">
.sidebar-proper {
height: max(100vh, 100%);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
box-shadow: 0 0 15px rgba(0, 0, 0, 0.25);
clip-path: inset(0px -15px 0px 0px);
overflow-y: hidden;
}

@ -1,40 +0,0 @@
<template>
<div>
<va-select class="multi-selector" :options="datalistOptions" v-model="selectedOptions"
@update:model-value="applyFilter" multiple searchable>
</va-select>
</div>
</template>
<script>
export default {
name: "datalist-filter",
data() {
return {
datalistOptions: [
// SELECT DISTINCT BTRIM(UNNEST(STRING_TO_ARRAY(datalist,','))) FROM geodata;
"Project",
"Rec",
"Data",
"Raw",
"Report",
],
selectedOptions: [],
}
},
methods: {
applyFilter() {
const filter = { "datalist": this.selectedOptions }
this.$emit('filter', filter, this.name)
},
},
}
</script>
<style lang="css">
.multi-selector {
--va-select-min-width: 12 rem;
}
</style>

@ -1,43 +0,0 @@
<template>
<div>
<va-select class="multi-selector" :options="descriptionOptions" v-model="selectedOptions"
@update:model-value="applyFilter" multiple searchable>
</va-select>
</div>
</template>
<script>
export default {
name: "description-filter",
data() {
return {
descriptionOptions: [
"терригенная порода-коллектор",
"черви",
"бусины из скорлупы страуса",
"осколки скорлупы яиц страуса",
"лопатка лощади ",
"грунты",
"глинисто-карбонатная порода",
"Искусстенная модель осадочных пород",
"угли",
],
selectedOptions: [],
}
},
methods: {
applyFilter() {
const filter = { "description": this.selectedOptions }
this.$emit('filter', filter, this.name)
},
},
}
</script>
<style lang="css">
.multi-selector {
--va-select-min-width: 12 rem;
}
</style>

@ -1,42 +0,0 @@
<template>
<div>
<va-select class="multi-selector" :options="formDimentionsOptions" v-model="selectedOptions"
@update:model-value="applyFilter" multiple searchable>
</va-select>
</div>
</template>
<script>
export default {
name: "form-dimentions-filter",
data() {
return {
formDimentionsOptions: [
"фрагмент 20 см",
"фрагменты 5-7 см",
"полноразмерный керн, 65 мм",
"цилиндр, 8 мм",
"цилиндр, 10 мм",
"цилиндр, 30 мм",
"полноразмерный керн",
"полноразмерный керн, 100 мм",
],
selectedOptions: [],
}
},
methods: {
applyFilter() {
const filter = { "form_dimentions": this.selectedOptions }
this.$emit('filter', filter, this.name)
},
},
}
</script>
<style lang="css">
.multi-selector {
--va-select-min-width: 12 rem;
}
</style>

@ -1,42 +0,0 @@
<template>
<div>
<va-select class="multi-selector" :options="projectOptions" v-model="selectedOptions"
@update:model-value="applyFilter" multiple searchable>
</va-select>
</div>
</template>
<script>
export default {
name: "project-filter",
data() {
return {
projectOptions: [
"AAK",
"AAM",
"ABB",
"ABE",
"AER",
"AJL",
"ASX",
"ATM",
],
selectedOptions: [],
}
},
methods: {
applyFilter() {
const filter = { "project": this.selectedOptions }
this.$emit('filter', filter, this.name)
},
},
}
</script>
<style lang="css">
.multi-selector {
--va-select-min-width: 12 rem;
}
</style>

@ -1,43 +0,0 @@
<template>
<div class="range-input">
<va-input placeholder="1.59" v-model="reso_min" input-class="text--center"
:mask="{ numeral: true, delimiter: '', numeralDecimalMark: ',' }" @change="applyFilter" />
<va-input placeholder="264" v-model="reso_max" input-class="text--center"
:mask="{ numeral: true, delimiter: '', numeralDecimalMark: ',' }" @change="applyFilter" />
</div>
</template>
<script>
export default {
name: "resolution-filter",
data() {
return {
// SELECT MIN(resolution), MAX(resolution) FROM geodata;
reso_min: "1.59",
reso_max: "264",
}
},
methods: {
applyFilter() {
const filter = { "resolution": [this.reso_min, this.reso_max] }
this.$emit('filter', filter, this.name)
},
},
}
</script>
<style lang="css">
.range-input {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 5rem
}
.range-input>div {
--va-input-wrapper-min-width: 4rem;
width: 4rem;
}
</style>

@ -22,7 +22,6 @@ import {
VaCard,
VaCardTitle,
VaCardContent,
VaCardActions,
VaImage,
VaSelect,
VaPagination,
@ -31,10 +30,6 @@ import {
VaChip,
VaModal,
VaPopover,
VaScrollContainer,
VaDropdown,
VaDropdownContent,
VaCheckbox,
} from 'vuestic-ui'
import 'vuestic-ui/dist/styles/essential.css'
import 'vuestic-ui/dist/styles/grid.css'
@ -71,7 +66,6 @@ app.use(createVuesticEssential({
VaCard,
VaCardTitle,
VaCardContent,
VaCardActions,
VaImage,
VaSelect,
VaPagination,
@ -80,10 +74,6 @@ app.use(createVuesticEssential({
VaChip,
VaModal,
VaPopover,
VaScrollContainer,
VaDropdown,
VaDropdownContent,
VaCheckbox,
}
}));

@ -1,5 +1,5 @@
<template>
<navbar class="navbar" @accountClick="$router.push('/admin/')">
<navbar @accountClick="$router.push('/admin/')">
<va-input placeholder="Начните вводить запрос" v-model="searchQuery"></va-input>
</navbar>
<div class="central-view">
@ -10,7 +10,7 @@
<overview-screen :items="filteredAndSearchedItems" :columns="filteredColumns" @mapClick="applyFilters" />
</div>
</div>
<login-modal :showModal="showModal" @closeModal="showModal = false" />
<login-modal :showModal="showModal" @closeModal="showModal=false" />
</template>
@ -39,15 +39,14 @@ export default {
"description",
"form_dimentions",
"datalist",
"resolution",
],
showColumns: [
"internal_id",
"category",
"description",
"stratum",
"form_dimentions",
"resolution",
"datalist"
"deposit",
"org",
"category",
"scanner",
],
showModal: false,
}
@ -90,28 +89,6 @@ export default {
if (item.depth_max < filterValues[0] || item.depth_min > filterValues[1]) {
return false;
}
} else if (filterParam === "resolution") {
if (
// we control resolution input format, so we can be reasonbly sure
// it has upto one comma to denote decimal point and no dots
// therefore it's safe to normalize it into
// JavaScript definition of a decimal literal
item.resolution < parseFloat(filterValues[0].replace(/,/, '.'))
|| item.resolution > parseFloat(filterValues[1].replace(/,/, '.'))
) {
return false;
}
} else if (filterParam === "datalist") {
// for all selected datalist options check if we can find it in the datalist string
for (let datalistOption of filterValues) {
if (item.datalist.search(datalistOption) === -1) {
return false;
}
}
} else if (filterParam === "project") {
if (!filterValues.includes(item["fadr"])) {
return false;
}
} else if (!filterValues.includes(item[filterParam])) {
return false;
}
@ -175,8 +152,4 @@ export default {
.main-view {
width: 100%;
}
.navbar {
z-index: 100;
}
</style>

@ -8,7 +8,7 @@
<item-details />
</div>
</div>
<login-modal :showModal="showModal" @closeModal="showModal = false" />
<login-modal :showModal="showModal" @closeModal="showModal=false" />
</template>
@ -47,12 +47,6 @@ export default {
route: "item-details",
hash: "#map",
},
{
title: "Похожие образцы",
icon: "view_list",
route: "item-details",
hash: "#similar",
},
{
title: "Связь с представителем",
icon: "contact_page",

Loading…
Cancel
Save