Merge pull request 'production' (#6) from production into main

Reviewed-on: #6
This commit is contained in:
vladislav 2026-09-03 13:46:27 +00:00
commit 3d91d41487
21 changed files with 850 additions and 206 deletions

View File

@ -56,11 +56,15 @@ async def send_delivery(
continue
# Уведомление получателю
if (exhibition:=existing.get('exhibition',{})) and (exhibition_id:=existing.get('exhibition_id')):
link = f'/profile/delivery/exhibition/{exhibition_id}'
else:
link = f'/profile/delivery'
notify = await notify_in_profile(NotifyItem(
user_id=str(existing["receiver"]["_id"]),
type_item="delivery",
title="Доставка картин",
link="/profile/delivery",
link=link,
text=f"{str(existing['sender']['username'])} — прислал вам картины."
))
@ -106,13 +110,16 @@ async def accept_delivery(
if not existing:
errors.append({"item": item_id, "error": "Доставка не найдена или не для вас"})
continue
if (exhibition:=existing.get('exhibition',{})) and (exhibition_id:=existing.get('exhibition_id')):
link = f'/profile/delivery/exhibition/{exhibition_id}'
else:
link = f'/profile/delivery'
# Уведомление отправителю
notify = await notify_in_profile(NotifyItem(
user_id=str(existing["sender"]["_id"]),
type_item="delivery",
title="Доставка картин",
link="/profile/delivery",
link=link,
text=f"Картина «{existing['title']}» получена — {str(existing['receiver']['username'])}."
))

View File

@ -564,12 +564,21 @@
" )\n",
" if exhibition_finished:\n",
" exhibition_id_str = str(exhibition_finished[\"_id\"])\n",
" cursor = delivery_collection.aggregate([\n",
" {\"$match\": {\n",
" \"exhibition_id\": exhibition_id_str, **prod_filter\n",
" }},\n",
" {\"$sort\": {\"_id\": -1}},\n",
" {\"$group\": {\n",
" \"_id\": \"$post_id\",\n",
" \"doc\": {\"$first\": \"$$ROOT\"}\n",
" }},\n",
" {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n",
" {\"$sort\": {\"_id\": -1}},\n",
" ])\n",
"\n",
" async for doc in delivery_collection.find(\n",
" {\"exhibition_id\": exhibition_id_str, \"status\": \"Получено\", **prod_filter},\n",
" {\"_id\": 0},\n",
" ):\n",
" sender = doc.get(\"sender\")\n",
" data = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n",
" for doc in data:\n",
" post_id = doc.get(\"post_id\")\n",
" exhibition_id = doc.get(\"exhibition_id\")\n",
"\n",
@ -578,9 +587,11 @@
" \"delivery_type\": \"\",\n",
" \"track\": \"\",\n",
" \"status\": \"Возврат художнику\",\n",
" \"receiver\": sender,\n",
" \"sender\": doc.get(\"receiver\"),\n",
" \"receiver\": doc.get(\"user\"),\n",
" \"sender\": doc.get(\"exhibition\", {}).get(\"user\"),\n",
" }\n",
" new_doc.pop('_id')\n",
" new_doc.pop('exhibition')\n",
"\n",
" await delivery_collection.update_one(\n",
" {**prod_filter, \"status\": \"Возврат художнику\", \"post_id\": post_id, \"exhibition_id\": exhibition_id},\n",
@ -1614,9 +1625,24 @@
"):\n",
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
" if exhibition:\n",
" posts = await delivery_collection.find({'status': 'На выставке', \"exhibition_id\": exhibition_id}).to_list()\n",
" match_ = {\n",
" \"exhibition_id\": exhibition_id,\n",
" 'prod': PROD,\n",
" }\n",
"\n",
" cursor = delivery_collection.aggregate([\n",
" {\"$match\": match_},\n",
" {\"$sort\": {\"_id\": -1}},\n",
" {\"$group\": {\n",
" \"_id\": \"$post_id\",\n",
" \"doc\": {\"$first\": \"$$ROOT\"}\n",
" }},\n",
" {\"$replaceRoot\": {\"newRoot\": \"$doc\"}},\n",
" {\"$sort\": {\"_id\": -1}},\n",
" ])\n",
" posts = [doc async for doc in cursor if doc.get('status') == 'На выставке']\n",
" return templates.TemplateResponse(\n",
" request=request, name=\"exhibition/index.html\", context={\"id\": 1, \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n",
" request=request, name=\"exhibition/index.html\", context={\"id\": 1, \"user\": user, \"exhibition\": exhibition, \"posts\": posts, 'now': datetime.now()}\n",
" )\n",
" else:\n",
" raise HTTPException(status_code=307, headers={\"Location\": '/competitions'}, detail=\"Конкурс не найден\")"
@ -1735,7 +1761,7 @@
" 'prod': PROD\n",
" }).sort({'_id':-1}).limit(1000).to_list()\n",
" return templates.TemplateResponse(\n",
" request=request, name=\"exhibitions.html\", context={\"id\": 1, \"user\": user, \"exhibitions\": exhibitions}\n",
" request=request, name=\"exhibitions.html\", context={\"id\": 1, \"user\": user, \"exhibitions\": exhibitions, 'now': datetime.now()}\n",
" )"
],
"display_name": ".venv"
@ -1817,11 +1843,7 @@
" ]})\n",
" \n",
" if not user:\n",
" # Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно\n",
" # Но для удобства использования мы можем сказать \"не найден\".\n",
" # Чтобы предотвратить перечисление, некоторые приложения всегда возвращают \"OK\", но отправляют письмо только если найдено.\n",
" # Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления.\n",
" raise HTTPException(status_code=404, detail=\"Пользователь с таким email не найден\")\n",
" return {'ok': True}\n",
" \n",
" # 4. Генерация токена\n",
" token = secrets.token_urlsafe(32)\n",
@ -1845,13 +1867,13 @@
" # 7. Асинхронная отправка email\n",
" try:\n",
" email_task.notify.delay(\n",
" recipient=user['email'],\n",
" context={\n",
" to_email=user['email'],\n",
" data={\n",
" 'link': reset_link, \n",
" 'email': user['email']\n",
" },\n",
" subject='Восстановление пароля',\n",
" template_name='restore.html'\n",
" template='restore.html'\n",
" )\n",
" except Exception as e:\n",
" # Логирование ошибки\n",
@ -3229,6 +3251,53 @@
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
"id": "8feec4b3",
"metadata": {},
"outputs": [],
"source": [
"@app.put(\"/api/post/{post_id}/sale\", response_model=dict)\n",
"async def sale_painting(\n",
" post_id: str,\n",
" current_user: Dict = Security(get_current_user)\n",
" ):\n",
" fin = [{'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}]\n",
" if 'partner' in current_user.get('roles', []):\n",
" fin.append({'_id': ObjectId(post_id)})\n",
" doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"Продано\"}})\n",
" if doc.modified_count: \n",
" existing = await delivery_collection.find_one(\n",
" {\"post_id\": post_id, \"status\": \"На выставке\"},\n",
" {\"_id\": 0}\n",
" )\n",
" if existing:\n",
" update_data = {\n",
" \"status\": \"Продано\",\n",
" \"post\": {\"status\": \"Продано\"},\n",
" }\n",
" notify = await notify_in_profile(NotifyItem(\n",
" user_id=str(existing[\"user\"][\"_id\"]),\n",
" type_item=\"delivery\",\n",
" title=\"Картина продана\",\n",
" link=\"/profile/delivery\",\n",
" text=f\"{str(existing.get('post',{}).get('title'))}\"\n",
" ))\n",
"\n",
" new_doc = {**existing, **update_data, \"notify\": notify.upserted_id}\n",
"\n",
" result = await delivery_collection.insert_one(new_doc)\n",
" else:\n",
" doc = await posts_collection.update_one({'$or': fin}, {'$set': {\"status\": \"В каталоге\"}})\n",
" await delivery_collection.delete_one(\n",
" {\"post_id\": post_id, \"status\": \"Продано\"}\n",
" )\n",
"\n",
" return {'ok': True}"
],
"display_name": ".venv"
},
{
"cell_type": "code",
"execution_count": null,
@ -4420,7 +4489,7 @@
" if field == 'name':\n",
" field = 'title'\n",
" if op == \"like\":\n",
" return {field: {\"$regex\": value, \"$options\": \"i\"}}\n",
" return {field: {\"$regex\": safe_strip(value), \"$options\": \"i\"}}\n",
" return {field: value}\n",
" mongo_query = mql_to_mongo(mql)\n",
" for post in [doc async for doc in posts_collection.find(mongo_query).sort({\"_id\":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]:\n",
@ -4800,7 +4869,7 @@
" ],\n",
" 'prod': PROD,\n",
" }},\n",
" {\"$sort\": {\"_id\": -1}}, # why not work\n",
" {\"$sort\": {\"_id\": -1}},\n",
" {\"$group\": {\n",
" \"_id\": \"$post_id\",\n",
" \"doc\": {\"$first\": \"$$ROOT\"}\n",
@ -4952,6 +5021,11 @@
"\n",
" galerists = [{\"_id\": uname, \"count\": counts_by_username.get(uname, '')} for uname in usernames]\n",
" posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)]\n",
" d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n",
" for post in posts:\n",
" for doc in d_posts:\n",
" if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n",
" post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n",
" return templates.TemplateResponse(\n",
" request=request, name=\"profile/partner/exhibition catalog.html\", context={\"now\": datetime.now(), \"user\": user, \"galerists\": galerists, \"genres\": genres, \"living_places\": living_places, \"exhibition\": exhibition, \"posts\": posts}\n",
" )\n"
@ -4977,7 +5051,7 @@
" d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()\n",
" for post in posts:\n",
" for doc in d_posts:\n",
" if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n",
" if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):\n",
" post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})\n",
" return templates.TemplateResponse(\n",
" request=request, name=\"profile/partner/exhibition.html\", context={\"now\": datetime.now(), \"user\": user, \"exhibition\": exhibition, \"posts\": posts}\n",

107
main.py
View File

@ -352,12 +352,21 @@ async def cron_every_five_min(token: str = Security(get_token)):
)
if exhibition_finished:
exhibition_id_str = str(exhibition_finished["_id"])
cursor = delivery_collection.aggregate([
{"$match": {
"exhibition_id": exhibition_id_str, **prod_filter
}},
{"$sort": {"_id": -1}},
{"$group": {
"_id": "$post_id",
"doc": {"$first": "$$ROOT"}
}},
{"$replaceRoot": {"newRoot": "$doc"}},
{"$sort": {"_id": -1}},
])
async for doc in delivery_collection.find(
{"exhibition_id": exhibition_id_str, "status": "Получено", **prod_filter},
{"_id": 0},
):
sender = doc.get("sender")
data = [doc async for doc in cursor if doc.get('status') == 'На выставке']
for doc in data:
post_id = doc.get("post_id")
exhibition_id = doc.get("exhibition_id")
@ -366,9 +375,11 @@ async def cron_every_five_min(token: str = Security(get_token)):
"delivery_type": "",
"track": "",
"status": "Возврат художнику",
"receiver": sender,
"sender": doc.get("receiver"),
"receiver": doc.get("user"),
"sender": doc.get("exhibition", {}).get("user"),
}
new_doc.pop('_id')
new_doc.pop('exhibition')
await delivery_collection.update_one(
{**prod_filter, "status": "Возврат художнику", "post_id": post_id, "exhibition_id": exhibition_id},
@ -1201,9 +1212,24 @@ async def exhibition_members(
):
exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})
if exhibition:
posts = await delivery_collection.find({'status': 'На выставке', "exhibition_id": exhibition_id}).to_list()
match_ = {
"exhibition_id": exhibition_id,
'prod': PROD,
}
cursor = delivery_collection.aggregate([
{"$match": match_},
{"$sort": {"_id": -1}},
{"$group": {
"_id": "$post_id",
"doc": {"$first": "$$ROOT"}
}},
{"$replaceRoot": {"newRoot": "$doc"}},
{"$sort": {"_id": -1}},
])
posts = [doc async for doc in cursor if doc.get('status') == 'На выставке']
return templates.TemplateResponse(
request=request, name="exhibition/index.html", context={"id": 1, "user": user, "exhibition": exhibition, "posts": posts}
request=request, name="exhibition/index.html", context={"id": 1, "user": user, "exhibition": exhibition, "posts": posts, 'now': datetime.now()}
)
else:
raise HTTPException(status_code=307, headers={"Location": '/competitions'}, detail="Конкурс не найден")
@ -1286,7 +1312,7 @@ async def exhibitions_all(
'prod': PROD
}).sort({'_id':-1}).limit(1000).to_list()
return templates.TemplateResponse(
request=request, name="exhibitions.html", context={"id": 1, "user": user, "exhibitions": exhibitions}
request=request, name="exhibitions.html", context={"id": 1, "user": user, "exhibitions": exhibitions, 'now': datetime.now()}
)
@app.get("/competitions", response_class=HTMLResponse)
@ -1350,11 +1376,7 @@ async def restore_api(
]})
if not user:
# Безопасность: всегда возвращать одинаковое время ответа/структуру сообщения, если возможно
# Но для удобства использования мы можем сказать "не найден".
# Чтобы предотвратить перечисление, некоторые приложения всегда возвращают "OK", но отправляют письмо только если найдено.
# Здесь мы придерживаемся вашей оригинальной логики, но помните о риске перечисления.
raise HTTPException(status_code=404, detail="Пользователь с таким email не найден")
return {'ok': True}
# 4. Генерация токена
token = secrets.token_urlsafe(32)
@ -1378,13 +1400,13 @@ async def restore_api(
# 7. Асинхронная отправка email
try:
email_task.notify.delay(
recipient=user['email'],
context={
to_email=user['email'],
data={
'link': reset_link,
'email': user['email']
},
subject='Восстановление пароля',
template_name='restore.html'
template='restore.html'
)
except Exception as e:
# Логирование ошибки
@ -2402,6 +2424,44 @@ async def post_painting(
raise HTTPException(status_code=500, detail=str(e))
@app.put("/api/post/{post_id}/sale", response_model=dict)
async def sale_painting(
post_id: str,
current_user: Dict = Security(get_current_user)
):
fin = [{'_id': ObjectId(post_id), 'user._id': current_user.get('_id')}]
if 'partner' in current_user.get('roles', []):
fin.append({'_id': ObjectId(post_id)})
doc = await posts_collection.update_one({'$or': fin}, {'$set': {"status": "Продано"}})
if doc.modified_count:
existing = await delivery_collection.find_one(
{"post_id": post_id, "status": "На выставке"},
{"_id": 0}
)
if existing:
update_data = {
"status": "Продано",
"post": {"status": "Продано"},
}
notify = await notify_in_profile(NotifyItem(
user_id=str(existing["user"]["_id"]),
type_item="delivery",
title="Картина продана",
link="/profile/delivery",
text=f"{str(existing.get('post',{}).get('title'))}"
))
new_doc = {**existing, **update_data, "notify": notify.upserted_id}
result = await delivery_collection.insert_one(new_doc)
else:
doc = await posts_collection.update_one({'$or': fin}, {'$set': {"status": "В каталоге"}})
await delivery_collection.delete_one(
{"post_id": post_id, "status": "Продано"}
)
return {'ok': True}
@app.put("/api/post/{post_id}", response_model=dict)
async def put_painting(
background_tasks: BackgroundTasks,
@ -3389,7 +3449,7 @@ async def get_posts(
if field == 'name':
field = 'title'
if op == "like":
return {field: {"$regex": value, "$options": "i"}}
return {field: {"$regex": safe_strip(value), "$options": "i"}}
return {field: value}
mongo_query = mql_to_mongo(mql)
for post in [doc async for doc in posts_collection.find(mongo_query).sort({"_id":-1}).limit(per_page * page + 8)][per_page * page - per_page:per_page * page]:
@ -3678,7 +3738,7 @@ async def profile_delivery(
],
'prod': PROD,
}},
{"$sort": {"_id": -1}}, # why not work
{"$sort": {"_id": -1}},
{"$group": {
"_id": "$post_id",
"doc": {"$first": "$$ROOT"}
@ -3806,6 +3866,11 @@ async def profile_partner_exhibitions_catalog(
galerists = [{"_id": uname, "count": counts_by_username.get(uname, '')} for uname in usernames]
posts = [doc async for doc in posts_collection.find().sort('_id', -1).limit(32)]
d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()
for post in posts:
for doc in d_posts:
if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):
post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})
return templates.TemplateResponse(
request=request, name="profile/partner/exhibition catalog.html", context={"now": datetime.now(), "user": user, "galerists": galerists, "genres": genres, "living_places": living_places, "exhibition": exhibition, "posts": posts}
)
@ -3823,7 +3888,7 @@ async def profile_partner_exhibitions(
d_posts = await delivery_collection.find({'post_id':{'$in':[str(i.get('_id')) for i in posts]}}).to_list()
for post in posts:
for doc in d_posts:
if str(post.get('_id')) == doc.get('post_id') and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):
if str(post.get('_id')) == doc.get('post_id') and post.get('status') not in ['Продано', 'Приглашение отклонено'] and doc.get('status') not in ['Приглашение отклонено'] and doc.get('receiver') and doc.get('receiver').get('_id') == str(user.get('_id')):
post.update({'status':doc.get('status'), 'receiver': doc.get('receiver'), 'd_id':doc.get('_id')})
return templates.TemplateResponse(
request=request, name="profile/partner/exhibition.html", context={"now": datetime.now(), "user": user, "exhibition": exhibition, "posts": posts}

View File

@ -1,85 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "90b31bac",
"metadata": {},
"outputs": [],
"source": [
"# ignore\n",
"import os\n",
"\n",
"def load_env(file_path):\n",
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n",
" with open(file_path) as f:\n",
" for line in f:\n",
" line = line.strip()\n",
" if not line or line.startswith('#'):\n",
" continue\n",
" \n",
" try:\n",
" key, value = line.split('=', 1)\n",
" key = key.strip()\n",
" value = value.strip('\"\\' ')\n",
" # Преобразование строковых значений в булевые\n",
" if value.lower() in ['true', '1', 'yes']:\n",
" value = True\n",
" elif value.lower() in ['false', '0', 'no']:\n",
" value = False\n",
" \n",
" # Проверка, что ключ не пустой\n",
" if key:\n",
" os.environ[key] = str(value) # Храним как строку\n",
" except ValueError:\n",
" print(f\"Ошибка: неверный формат в строке '{line}'\")\n",
"\n",
"try:\n",
" load_env('env')\n",
"except:\n",
" load_env('env_example')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3f5835c",
"metadata": {},
"outputs": [],
"source": [
"from db import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d80807dd",
"metadata": {},
"outputs": [],
"source": [
"await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}})"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -1,3 +0,0 @@
from db import *
await posts_collection.update_many({'status':'publish'},{'$set':{'status':'В каталоге'}})

View File

@ -2,7 +2,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox
workbox.setConfig({ debug: false });
const CACHE_NAME = 'app-cache-v2.1.4';
const CACHE_NAME = 'app-cache-v2.1.5';
const MIN_VALID_SIZE = 500;
const ASSETS_TO_CACHE = [

View File

@ -2,7 +2,7 @@ importScripts('https://storage.googleapis.com/workbox-cdn/releases/7.0.0/workbox
workbox.setConfig({ debug: false });
const CACHE_NAME = 'app-cache-v2.1.4';
const CACHE_NAME = 'app-cache-v2.1.5';
const MIN_VALID_SIZE = 500;
const ASSETS_TO_CACHE = [

View File

@ -676,14 +676,17 @@
" if other_ids:\n",
" await delivery_collection.update_many(\n",
" {'post_id': {'$in': other_ids}},\n",
" {'$set': {'status': 'Приглашение откланено'}} # как вы написали\n",
" {'$set': {'status': 'Приглашение отклонено'}} # как вы написали\n",
" )\n",
"\n",
" if (exhibition:=invitation.get('exhibition',{})) and (exhibition_id:=invitation.get('exhibition_id')):\n",
" link = f'/profile/delivery/exhibition/{exhibition_id}'\n",
" else:\n",
" link = f'/profile/delivery'\n",
" await notify_in_profile(NotifyItem(\n",
" user_id=invitation.get('exhibition',{}).get(\"user\",{}).get('_id'),\n",
" user_id=exhibition.get(\"user\",{}).get('_id'),\n",
" type_item='c',\n",
" title=f'Выставка \"{invitation.get('exhibition',{}).get(\"title\")}\"',\n",
" link=f'/profile/delivery',\n",
" title=f'Выставка \"{exhibition.get(\"title\")}\"',\n",
" link=link,\n",
" text=f'Художник принял условия'\n",
" ))"
],
@ -700,7 +703,7 @@
" exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})\n",
" exhibition_owner = exhibition.get('user')\n",
" obj_ids = [ObjectId(x) for x in exhibition.get('posts', [])]\n",
" posts = await posts_collection.find({\"_id\": {\"$in\": obj_ids}}).to_list() if obj_ids else []\n",
" posts = await posts_collection.find({\"_id\": {\"$in\": obj_ids}, 'status': 'В каталоге'}).to_list() if obj_ids else []\n",
" new_posts = {}\n",
" all_posts = {}\n",
" for post in posts:\n",

View File

@ -514,14 +514,17 @@ async def accept_invitations(invitations_id: str, posts_ids: List[str]):
if other_ids:
await delivery_collection.update_many(
{'post_id': {'$in': other_ids}},
{'$set': {'status': 'Приглашение откланено'}} # как вы написали
{'$set': {'status': 'Приглашение отклонено'}} # как вы написали
)
if (exhibition:=invitation.get('exhibition',{})) and (exhibition_id:=invitation.get('exhibition_id')):
link = f'/profile/delivery/exhibition/{exhibition_id}'
else:
link = f'/profile/delivery'
await notify_in_profile(NotifyItem(
user_id=invitation.get('exhibition',{}).get("user",{}).get('_id'),
user_id=exhibition.get("user",{}).get('_id'),
type_item='c',
title=f'Выставка "{invitation.get('exhibition',{}).get("title")}"',
link=f'/profile/delivery',
title=f'Выставка "{exhibition.get("title")}"',
link=link,
text=f'Художник принял условия'
))
@ -529,7 +532,7 @@ async def exhibition_send_invitations(exhibition_id):
exhibition = await exhibition_collection.find_one({'_id': ObjectId(exhibition_id)})
exhibition_owner = exhibition.get('user')
obj_ids = [ObjectId(x) for x in exhibition.get('posts', [])]
posts = await posts_collection.find({"_id": {"$in": obj_ids}}).to_list() if obj_ids else []
posts = await posts_collection.find({"_id": {"$in": obj_ids}, 'status': 'В каталоге'}).to_list() if obj_ids else []
new_posts = {}
all_posts = {}
for post in posts:

View File

@ -37,7 +37,6 @@
max-width: 100%;
overflow-wrap: break-word;
word-break: break-word;
hyphens: auto;
}
.c_qx7jk{margin: 100px 0;}
.c_h9bsw{width: 100%;object-fit: scale-down;}

View File

@ -18,7 +18,6 @@ h1,h2,h3,p {
max-width: 100%;
overflow-wrap: break-word;
word-break: break-word;
hyphens: auto;
}
html{display: flex;justify-content: center;}
input, button {touch-action: manipulation;}

View File

@ -149,7 +149,7 @@ document.addEventListener('DOMContentLoaded', () => {
// 2) newest first
const aTime = parseDate(a.dateStr)?.getTime() ?? 0;
const bTime = parseDate(b.dateStr)?.getTime() ?? 0;
return bTime - aTime;
return aTime - bTime;
});
notificationsArray.forEach(item => {

View File

@ -11,11 +11,8 @@
<section class="c_72077">
<div class="c_q7xgt b_vnic7">
<div class="c_w3iz6">
<div class="c_k9ibf b_6fwyq" style="grid-area: b;">
<div class="c_k9ibf b_6fwyq" style="grid-area: b;padding: 0;">
<div class="b_ornr9">
<div
style="position:absolute;inset:0;background:url('/b/exhibition/{{ exhibition.poster_file_id }}') no-repeat center/cover;filter:blur(3px)">
</div>
<picture>
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}" class="b_ornr9"
src="/b/exhibition/{{ exhibition.poster_file_id }}" alt="Выставка {{exhibition.title }}">
@ -30,14 +27,16 @@
</svg>
<span class="b_ww2ac">Назад</span>
</a>
{% if exhibition.get('status',-1) == 2 %}
<div class="b_thlt1_2">
<span class="b_5dr47_2">Выставка закончилась</span>
</div>
<div class="{% if now > exhibition.date_finish %}b_vabj9_2{%else%}b_vabj9{% endif %}">
{% if now > exhibition.date_finish %}
<span class="c_95csi b_0jst1_2">Закончилась</span>
{% elif now > exhibition.datetime_start %}
<span class="c_95csi b_0jst1">Началась</span>
{% else %}
<div class="b_thlt1">
<span class="b_5dr47">Идет выставка</span>
<span class="c_95csi b_0jst1">Формируется</span>
{% endif %}
</div>
{% if now < exhibition.date_finish %}
<span class="b_sdf">До {{ (exhibition.date_finish.strftime('%d.%m.%Y')) if exhibition.date_finish else ''}}</span>
{% endif %}
</div>
@ -95,6 +94,15 @@
<header class="c_zjvne b_tt84a">
<h2 class="c_6kn8d b_uilob">{{post.title}}</h2>
</header>
{% if user and (user._id == post.user._id or 'partner' in user.get('roles',[]))%}
<label class="offer-label delivery-checkbox-label">
<input type="checkbox"
onchange="saledPaintings('{{post._id}}')"
class="offer-checkbox delivery-checkbox send-checkbox"
{% if post.status == 'Продано' %}checked{%endif%}>
<span>Продано</span>
</label>
{% endif %}
<div class="b_xz6em" ><img class="b_ob59y" src="/a/{{post.user._id}}" crossorigin="anonymous" fetchpriority="high" loading="eager">
<div class="c_9hjdk b_ffruz">
<p class="b_qla2q">{{post.city or ''}}</p>
@ -109,7 +117,38 @@
{% endfor %}
</div>
<script>
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
})
.then(response => {
if (response.ok) {
// Find and delete the parent figure
const postElement = document.querySelector(`figure[data-postId='${postId}']`);
showToast({
message: 'Сохранено!',
status: 'info',
duration: 1200
});
} else {
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
}
})
.catch(error => {
// Handle network errors
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
});
}
</script>
<script>
(function(){
const input = document.getElementById('memberSearch');
@ -157,6 +196,11 @@
<meta property="og:site_name" content="Конкурсы | KULTMOST.АВТОПОСТ">
{{ header.style() }}
<style>
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_0jst1_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9_2 { align-items: center; background: #5e17982f; border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_ornr9{ border-radius: 10px; cursor: zoom-in; }
.b_thlt1 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_5dr47 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_thlt1_2 { align-items: center; background: rgba(95, 23, 152, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }

View File

@ -22,9 +22,6 @@
<a href="{{link}}">
{% endif %}
<div class="b_ornr9">
<div
style="position:absolute;inset:0;background:url('/b/exhibition/{{ exhibition.poster_file_id }}') no-repeat center/cover;filter:blur(3px)">
</div>
<picture>
<img crossorigin="anonymous" fetchpriority="high" loading="eager" alt="{{ exhibition.title }}" class="b_ornr9"
src="/b/exhibition/{{ exhibition.poster_file_id }}" alt="Выставка {{exhibition.title }}">
@ -37,16 +34,15 @@
{% if link %}
<a href="{{link}}" class="b_73p10">
{% endif %}
{% if exhibition.get('status',-1) == 2 %}
<div class="b_vabj9_2">
<span class="b_0jst1_2">Выставка закончилась</span>
</div>
<div class="{% if now > exhibition.date_finish %}b_vabj9_2{%else%}b_vabj9{% endif %}">
{% if now > exhibition.date_finish %}
<span class="c_95csi b_0jst1_2">Закончилась</span>
{% elif now > exhibition.datetime_start %}
<span class="c_95csi b_0jst1">Началась</span>
{% else %}
<div class="b_vabj9">
<span class="b_0jst1">Идет выставка</span>
</div>
<!-- <span class="b_sdf">До {{ (exhibition.date_finish.strftime('%d.%m.%Y')) if exhibition.date_finish else ''}}</span> -->
<span class="c_95csi b_0jst1">Формируется</span>
{% endif %}
</div>
<header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh">{{ exhibition.title }}</h3>
</header>
@ -110,7 +106,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.fl{display: flex;justify-content: space-between;width: 100%;}
.c_som9s { color: #FFF; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
.c_aq9et { display: flex; padding: 19px 33px; justify-content: center; align-items: center; align-self: stretch; border-radius: 5px; background: #5F1798; }
.b_ornr9 { align-self: stretch; height: 400px; overflow: hidden;}
.b_ornr9 { align-self: stretch; overflow: hidden;}
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_0jst1_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }

View File

@ -145,7 +145,11 @@
.c_la1ap { color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
.c_w6ecf { -webkit-box-orient: vertical; -webkit-line-clamp: 1; align-self: stretch; color: #000; display: -webkit-box; font-family: Inter; font-size: 20px; font-style: normal; font-weight: 700; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.c_m3jeo { align-content: flex-start; align-items: flex-start; background: #FFF; display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; width: 1600px; }
}</style>
}
.b_gagrr { align-items: center; align-self: stretch; display: flex; justify-content: space-between; }
.b_rhbo8 { color: #5F1798; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; }
.c_eiyz0{cursor: pointer;}
</style>
{{ footer.style() }}
{% endblock %}
@ -182,14 +186,59 @@
</div>
<p class="c_zlnpy b_7efyd">{{paint.description or ''}}</p>
<p class="c_sbnel b_frv5u">{{ (paint._id.generation_time.strftime('%d.%m.%Y')) if paint._id else ''}}</p>
<label class="offer-label delivery-checkbox-label">
<input type="checkbox"
onchange="saledPaintings('{{paint._id}}')"
class="offer-checkbox delivery-checkbox send-checkbox"
{% if paint.status == 'Продано' %}checked{%endif%}>
<span>Продано</span>
</label>
{% if user and user._id == paint.user._id %}
<div class="c_ybfp2 b_gagrr" style="max-width: 320px;">
<a class="c_eiyz0 b_rhbo8" href="/form/post/{{paint._id}}">Редактировать</a>
<button class="c_eiyz0 b_rhbo8" onclick="deletePost('{{paint._id}}')">Удалить</button>
</div>
{% else %}
<a class="c_75ykb b_gk0ob" href="https://telegram.me/gennadiy0605"><span class="c_dlut5 b_v0d1u">Обсудить покупку картины </span></a>
{% endif %}
<div class="c_sbnel b_frv5u">{{ share.share('/paintings/{}'.format(paint._id), paint.title, '/b/posts/{}'.format(paint.medias[0])) }}</div>
<p class="c_sbnel b_frv5u">👁 {{total_watch_count}} (+{{today_watch_count}} Сегодня)</p>
</div>
</section>
</div>
<script>
<script>
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
})
.then(response => {
if (response.ok) {
// Find and delete the parent figure
const postElement = document.querySelector(`figure[data-postId='${postId}']`);
showToast({
message: 'Сохранено!',
status: 'info',
duration: 1200
});
} else {
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
}
})
.catch(error => {
// Handle network errors
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
});
}
</script>
<script>
const images = [
{% for media in paint.medias_original %}
{ src: "/b/posts/{{media}}" },
@ -296,9 +345,35 @@
init();
setTimeout(updateTransform, 100); // даём браузеру время на рендер
</script>
<script>
function deletePost(postId) {
fetch(`/api/post/${postId}`, {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
// Find and delete the parent figure
const postElement = document.querySelector(`figure[data-postId='${postId}']`);
if (postElement) {
postElement.remove();
}
} else {
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
}
})
.catch(error => {
// Handle network errors
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
});
}
</script>
{{ footer.section(user) }}
{% endblock %}

View File

@ -44,7 +44,7 @@
<span class="b_5dr47_2">{{doc.status}}</span>
</div>
{% endif %}
{% if doc.type == 'exhibition' and doc.get('exhibition') %}
{% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}
<p class="b_6spaf">{{doc.exhibition.title}}</p>
<p>Место проведения: {{ doc.exhibition.get('place') }}</p>
<div class="b_xz6em">
@ -54,6 +54,57 @@
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
</div>
</div>
{% if doc.get('user') and user._id == doc.user._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{% else %}
{% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p>
@ -75,6 +126,57 @@
<span class="c_zm0m4 b_09est">{{doc.sender.username}}</span>
</div>
</div>
{% if user._id != doc.sender._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.sender._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.sender.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.sender.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.sender.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.sender.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.sender.vk_id %}https://vk.ru/im?sel={{doc.sender.vk_id}}{%else%}#{%endif%}"
{% if not doc.sender.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.sender.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{% endif %}
{% if doc.receiver %}
<div class="b_xz6em">
@ -85,6 +187,57 @@
<span class="b_qla2q">+7 *** *** {{ doc.receiver.phone[-4:] }}</span>
</div>
</div>
{% if user._id != doc.receiver._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{% endif %}
{% endif %}
<p class="b_6spaf">

View File

@ -31,8 +31,16 @@
<a class="b_6spaf" href="/profile/delivery/{{doc.post_id}}#main">Посмотреть историю</a>
<h2 class="c_9hvtd b_f5fb2">{{doc.title}}</h2>
{% if doc.type == 'exhibition' and doc.get('exhibition') %}
{% if doc.status == 'На выставке' %}
<label class="offer-label">
<input type="checkbox"
onchange="saledPaintings('{{doc.post_id}}')"
class="offer-checkbox"
{% if doc.status == 'Продано' %}checked{%endif%}>
<span>Продано</span>
</label>
{% endif %}
{% if doc.type == 'exhibition' and doc.get('exhibition') or doc.status == 'Продано' %}
<p class="b_6spaf">{{doc.get('exhibition').title}}</p>
<div class="b_xz6em">
<img src="/a/{{doc.receiver._id}}" class="b_ob59y">
@ -41,6 +49,57 @@
<span class="c_zm0m4 b_09est">{{doc.receiver.username}}</span>
</div>
</div>
{% if doc.get('user') and user._id == doc.user._id%}
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{doc.receiver._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{doc.receiver.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ doc.receiver.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{doc.receiver.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{doc.receiver.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if doc.receiver.vk_id %}https://vk.ru/im?sel={{doc.receiver.vk_id}}{%else%}#{%endif%}"
{% if not doc.receiver.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{doc.receiver.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% endif %}
{% else %}
{% if doc.address %}
<p class="b_6spaf">Адрес: {{doc.address}}</p>
@ -216,15 +275,47 @@
<div class="c_ru432">
{{ profile_nav.profile_nav(user, 'delivery') }}
<div class="c_tihtk" id="main">
{% set statuses = ['Ожидает отправки', 'Возврат художнику'] %}
{% if data | selectattr('status', 'in', statuses) | list | length > 0 %}
{% set statusAccept = ['Ожидает получения'] %}
{% set first =
data
| selectattr('status', 'in', statuses)
| list
| length > 0
%}
{% set second =
(
data
| selectattr('status', 'in', statusAccept)
| list
| length > 0
)
and
(
data
| selectattr('receiver._id', 'equalto', user._id)
| list
| length > 0
)
%}
{% if first or second %}
<article class="c_9fgdh b_ax8vz">
<div class="c_z3dpx b_73p10">
{% if first %}
<header class="c_m876y b_9xtz9">
<h3 class="b_8f3uh">Настройки отправки</h3>
</header>
<p class="b_tfnz2">Можно менять перед каждой отправкой картины</p>
<label class="offer-label">
<input
type="checkbox"
class="offer-checkbox"
onchange="selectAll(this, true)"
>
<span class="offer-text">Выбрать все картины</span>
</label>
<div class="b_73p10">
<div class="c_aihm6 floating-group">
<select class="c_xhd5k" id="delivery_type" name="delivery_type" required>
@ -239,6 +330,20 @@
<label for="track" class="c_z3ybu floating-label">Трэк-номер</label>
</div>
</div>
{% endif %}
{% if second %}
<header class="c_m876y b_9xtz9">
<h3 class="b_8f3uh">Настройки получения</h3>
</header>
<label class="offer-label">
<input
type="checkbox"
class="offer-checkbox"
onchange="selectAll(this, false)"
>
<span class="offer-text">Выбрать все картины</span>
</label>
{% endif %}
</div>
</article>
{% endif %}
@ -250,12 +355,72 @@
{# Sticky bottom action bar #}
<div class="st" style="z-index: 2000;min-height: 101px;padding: 25px;gap: 5px;">
<button class="b_wy3rt ba" id="btnSendSelected" style="display: none;" onclick="btnSendSelected()">
<span class="b_bvl0d">Отправить выбранные</span>
<span class="b_bvl0d">Отправить выбранные (<span id="sendCount">0</span>)</span>
</button>
<button class="b_wy3rt ba" id="btnAcceptSelected" style="display: none;" onclick="btnAcceptSelected()">
<span class="b_bvl0d">Получить выбранные</span>
<span class="b_bvl0d">Получить выбранные (<span id="acceptCount">0</span>)</span>
</button>
</div>
<script>
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
})
.then(response => {
if (response.ok) {
// Find and delete the parent figure
const postElement = document.querySelector(`figure[data-postId='${postId}']`);
showToast({
message: 'Сохранено!',
status: 'info',
duration: 1200
});
} else {
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
}
})
.catch(error => {
// Handle network errors
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
});
}
</script>
<script>
function selectAll(elem, send=true){
if (send){
if (elem.checked){
document.querySelectorAll('.send-checkbox').forEach(el=>{
el.checked = true;
})
} else {
document.querySelectorAll('.send-checkbox').forEach(el=>{
el.checked = false;
})
}
} else {
if (elem.checked){
document.querySelectorAll('.accept-checkbox').forEach(el=>{
el.checked = true;
})
} else {
document.querySelectorAll('.accept-checkbox').forEach(el=>{
el.checked = false;
})
}
}
updateStickyButtons();
}
</script>
<script>
// ─────────────────────────────────────────────
// Helpers: collect selected IDs
@ -282,6 +447,12 @@
const sendBtn = document.getElementById('btnSendSelected');
const acceptBtn = document.getElementById('btnAcceptSelected');
const sendCountSpan = document.getElementById('sendCount');
const acceptCountSpan = document.getElementById('acceptCount');
sendCountSpan.textContent = sendCount;
acceptCountSpan.textContent = acceptCount;
if (sendBtn) sendBtn.style.display = sendCount > 0 ? 'inline-flex' : 'none';
if (acceptBtn) acceptBtn.style.display = acceptCount > 0 ? 'inline-flex' : 'none';
}
@ -712,7 +883,7 @@ picture>img{ width: 100%; object-fit: scale-down; }
.b_s6ool { white-space: pre-line; -webkit-box-orient: vertical; -webkit-line-clamp: 3; align-self: stretch; color: rgba(58, 58, 58, 0.75); display: -webkit-box; font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; overflow: hidden; text-overflow: ellipsis; }
.b_mp1wb { align-self: stretch; color: rgba(58, 58, 58, 0.75); font-family: Inter; font-size: 14px; font-style: normal; font-weight: 400; line-height: normal; text-align: right; }
.b_73p10 { align-items: flex-start; align-self: stretch; background: #FFF; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
.c_z3dpx{ padding: 10px 20px;}
.c_z3dpx{ padding: 10px 20px; gap: 20px; }
.b_ax8vz { align-items: flex-start; border-radius: 10px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.30), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; width: 335px; overflow: hidden;}
@media (min-width: 901px) {
.c_9fgdh { align-items: flex-start; border-radius: 10px; border: 1px solid #5F1798; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.30), 0 4px 20px 0 rgba(0, 0, 0, 0.20); display: flex; flex-direction: column; flex-shrink: 0; width: 335px; }

View File

@ -18,7 +18,7 @@
<div class="c_tihtk" id="main">
{% for i in data %}
<article class="c_9fgdh b_ax8vz">
<a href="/exhibition/{{i._id}}{% if i.status == 0 %}/catalog{%endif%}#main">
<a href="/exhibition/{{i._id}}">
<div class="b_ornr9">
<picture>
<img src="/b/exhibition/{{i.poster_file_id}}" data-src="/b/exhibition/{{i.poster_orig_id}}" id="preview_poster" crossorigin="anonymous" fetchpriority="high" loading="eager" alt="Загрузите афишу" class="b_ornr9">
@ -26,7 +26,7 @@
</div>
</a>
<div class="c_z3dpx b_73p10">
<a class="b_73p10" href="/exhibition/{{i._id}}{% if i.status == 0 %}/catalog{%endif%}#main">
<a class="b_73p10" href="/exhibition/{{i._id}}">
<div class="c_c4wam b_vabj9">
{% if now > i.date_finish %}
<span class="c_95csi b_0jst1">Закончилась</span>

View File

@ -158,6 +158,10 @@
<header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh">{{ doc.title }}</h3>
</header>
{% if doc.get('type') == 'Выставка' %}
<img src="/b/exhibition/{{doc.get('exhibition').get('poster_orig_id')}}"
style="max-width: 100%;">
{%endif%}
<p class="c_2fr39 b_tfnz2">{{doc.get('exhibition').title }}</p>
<p>Место проведения: {{ doc.get('exhibition').get('place') }}</p>
<div class="b_otttb">
@ -169,10 +173,7 @@
''}}</span>
</div>
{{render_links(doc)}}
<p>Картин выбрано: <span id="lenSelected">0</span></p>
<button onclick="accept_inv('{{doc._id}}')" class="btn" style="width: 100%; box-sizing: border-box;">
Принять условия
</button>
{% set user_contact = doc.get('user') %}
{{ render_user_contacts(user_contact) }}
<p class="c_drpqj b_mp1wb">{{ (doc._id.generation_time.strftime('%d.%m.%Y')) if doc._id else ''}}</p>
@ -181,6 +182,40 @@
{% for post in doc.get('posts')%}
{{render_post(post)}}
{% endfor %}
<div class="st" style="z-index: 2000;min-height: 101px;padding: 22px;gap: 5px;">
<div style="
display: flex;
gap: 10px;
align-items: center;
">Картин выбрано: <span id="lenSelected">0</span> <label class="offer-label delivery-checkbox-label">
<input type="checkbox"
class="offer-checkbox delivery-checkbox accept-checkbox"
onchange="selecteAll(this)"
data-id="{{doc._id}}">
<span>Все</span>
</label></div>
<button onclick="accept_inv('{{doc._id}}')" class="btn" style="width: 100%; box-sizing: border-box;">
Принять условия
</button>
</div>
<script>
function selecteAll(elem){
if (elem.checked){
document.querySelectorAll('.yes').forEach(el=>{
el.checked = true;
selectPost(el)
})
} else {
document.querySelectorAll('.yes').forEach(el=>{
el.checked = false;
uSelectPost(el)
})
}
updateSelectedCount()
}
</script>
{%endif%}
{% if doc.get('type_')=='paint' %}
{% set statuses = ['Ожидает отправки', 'Возврат художнику'] %}
@ -216,14 +251,7 @@
{{ render_doc(doc) }}
{% endfor %}
{# Sticky bottom action bar #}
<div class="st" style="z-index: 2000;min-height: 101px;padding: 25px;gap: 5px;">
<button class="b_wy3rt ba" id="btnSendSelected" style="display: none;" onclick="btnSendSelected()">
<span class="b_bvl0d">Отправить выбранные</span>
</button>
<button class="b_wy3rt ba" id="btnAcceptSelected" style="display: none;" onclick="btnAcceptSelected()">
<span class="b_bvl0d">Получить выбранные</span>
</button>
</div>
<script>
function toggleTrackField() {
const deliveryType = document.getElementById('delivery_type');
@ -714,6 +742,8 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="tel:{{user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg"
height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
@ -750,6 +780,7 @@ document.getElementById('delivery_type')?.addEventListener('change', toggleTrack
</a>
</div>
</div>
</details>
{% endmacro %}
{% macro render_links(doc) %}
{% if doc.get('exhibition')%}

View File

@ -29,6 +29,8 @@
<a class="btn ba" href="/profile/invitation/{{doc._id}}#main" style="width: 100%;">Открыть</a>
{% endif %}
{% if doc.get('type') == 'Выставка' %}
<img src="/b/exhibition/{{doc.get('exhibition').get('poster_orig_id')}}"
style="max-width: 100%;">
<p class="c_2fr39 b_tfnz2">{{doc.get('exhibition').title }}</p>
<p>Место проведения: {{ doc.get('exhibition').get('place') }}</p>
<div class="b_otttb">
@ -107,6 +109,8 @@ async function accept_inv(inv_id) {
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="tel:{{user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg"
height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
@ -143,6 +147,7 @@ async function accept_inv(inv_id) {
</a>
</div>
</div>
</details>
{% endmacro %}
{% block head %}
{{ header.style() }}

View File

@ -75,8 +75,17 @@
<header class="c_m876y b_9xtz9">
<h3 class="c_hysjg b_8f3uh" id="preview_title">{{post.title}}</h3>
</header>
{% if post.status == 'На выставке' %}
<label class="offer-label">
<input type="checkbox"
onchange="saledPaintings('{{post._id}}')"
class="offer-checkbox"
{% if post.status == 'Продано' %}checked{%endif%}>
<span>Продано</span>
</label>
{% endif %}
{% if post.status=='В каталоге' %}
<div class="l_green"><span>{{post.status}}</span></div>
<div class="l_green" id="SC"><span>{{post.status}}</span></div>
{% else %}
<div class="l_purple"><span>{{post.status}}</span></div>
{% endif %}
@ -92,6 +101,55 @@
</div>
{% endif %}
</div>
<details style="margin-bottom: 1.25rem;">
<summary>Показать контакты</summary>
<div class="menua">
<a class="icona" href="/artists/{{post.user._id}}">
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M234-276q51-39 114-61.5T480-360q69 0 132 22.5T726-276q35-41 54.5-93T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 59 19.5 111t54.5 93Zm146.5-204.5Q340-521 340-580t40.5-99.5Q421-720 480-720t99.5 40.5Q620-639 620-580t-40.5 99.5Q539-440 480-440t-99.5-40.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm100-95.5q47-15.5 86-44.5-39-29-86-44.5T480-280q-53 0-100 15.5T294-220q39 29 86 44.5T480-160q53 0 100-15.5ZM523-537q17-17 17-43t-17-43q-17-17-43-17t-43 17q-17 17-17 43t17 43q17 17 43 17t43-17Zm-43-43Zm0 360Z" />
</svg>
<span>Профиль</span>
</a>
<a class="icona" href="tel:{{post.user.phone.split(',')[-1].strip()}}"><svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M162-120q-18 0-30-12t-12-30v-162q0-13 9-23.5t23-14.5l138-28q14-2 28.5 2.5T342-374l94 94q38-22 72-48.5t65-57.5q33-32 60.5-66.5T681-524l-97-98q-8-8-11-19t-1-27l26-140q2-13 13-22.5t25-9.5h162q18 0 30 12t12 30q0 125-54.5 247T631-329Q531-229 409-174.5T162-120Zm556-480q17-39 26-79t14-81h-88l-18 94 66 66ZM360-244l-66-66-94 20v88q41-3 81-14t79-28Zm358-356ZM360-244Z" />
</svg>
<span>{{ post.user.phone.split(',')[-1].strip() }}</span>
</a>
<a class="icona" href="mailto:{{post.user.email}}"> <svg xmlns="http://www.w3.org/2000/svg" height="24px"
viewBox="0 -960 960 960" width="24px" fill="#5F1798">
<path
d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480v58q0 59-40.5 100.5T740-280q-35 0-66-15t-52-43q-29 29-65.5 43.5T480-280q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480v58q0 26 17 44t43 18q26 0 43-18t17-44v-58q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93h200v80H480Zm85-315q35-35 35-85t-35-85q-35-35-85-35t-85 35q-35 35-35 85t35 85q35 35 85 35t85-35Z" />
</svg>
<span>{{post.user.email}}</span>
</a>
<div class="icona"><span>Написать</span></div>
<div class="icona">
<a class="icona" href="{% if post.user.vk_id %}https://vk.ru/im?sel={{post.user.vk_id}}{%else%}#{%endif%}"
{% if not post.user.vk_id %}onclick="showToast({message: 'У пользователя не привязан ВК'})"{%endif%}>
<img src="/static/icons/vk.svg" height="40">
</a>
<a class="icona" href="https://telegram.me/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/tg.svg" height="40">
<!-- <span>Написать в ТГ</span> -->
</a>
<a class="icona" href="https://max.ru/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/max.svg" height="40" style="border-radius: 50% 50% 50% 30%;">
<!-- <span>Написать в MAX</span> -->
</a>
<a class="icona" href="https://wa.me/{{post.user.phone.split(',')[-1].strip()}}">
<img src="/static/icons/wapp.svg" height="40">
<!-- <span>Написать в WA</span> -->
</a>
</div>
</div>
</details>
{% if post.status=='В каталоге' %}
<button onclick="selectPost(this, '{{post._id}}')" class="c_ctfod b_289i1">
<span class="c_d2l0b b_al8e7">Убрать с выставки</span>
@ -115,6 +173,38 @@
</div>
</div>
</section>
<script>
function saledPaintings(postId) {
fetch(`/api/post/${postId}/sale`, {
method: 'PUT'
})
.then(response => {
if (response.ok) {
// Find and delete the parent figure
const postElement = document.querySelector(`figure[data-postId='${postId}']`);
showToast({
message: 'Сохранено!',
status: 'info',
duration: 1200
});
} else {
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
}
})
.catch(error => {
// Handle network errors
showToast({
message: 'Ошибка соединения с сервером!',
status: 'error',
duration: 2500
});
});
}
</script>
{#
<script>
async function btnAccept(btn, itemId){
@ -161,6 +251,10 @@
async function sendInvitations(exhibition_id) {
const data = new FormData();
data.set('exhibition_id', exhibition_id);
document.querySelectorAll('#SC').forEach(el=>{
el.className = 'l_purple';
el.querySelector('span').textContent = 'Ожидает принятия условий';
})
try {
const res = await fetch('/api/exhibition/send/invitations', {
method: 'POST',
@ -211,8 +305,21 @@
{{ profile_nav.style() }}
{{ competition.style() }}
<style>
.menua{width: 100%;
display: flex;
flex-direction: column;
gap: 7px;
margin: 10px 20px;}
.icona{display: flex;
gap: 10px;
align-items: center;}
.icona>span{color: #5F1798;}
{{label.green()}}
{{label.purple()}}
.b_0jst1 { color: #0ED646; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9 { align-items: center; background: rgba(14, 214, 70, 0.20); border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
.b_0jst1_2 { color: #5F1798; font-family: Inter; font-size: 12px; font-style: normal; font-weight: 700; line-height: normal; }
.b_vabj9_2 { align-items: center; background: #5e17982f; border-radius: 30px; display: flex; justify-content: center; padding: 1.5px 10px; }
#preview_poster{min-height: 456px;}
body{background: #F6EBFF;}
.c_3ihvi{ position: sticky; top: 0; z-index: 2; background: white; }