Compare commits

...

17 Commits

Author SHA1 Message Date
Kseninia Mikhaylova 0b3e0b615c styles 2024-11-20 16:44:19 +03:00
Kseninia Mikhaylova fc26f3b08c sort 2024-11-20 16:36:46 +03:00
Kseninia Mikhaylova a4378b52f7 style 2024-11-20 16:32:58 +03:00
Kseninia Mikhaylova 613d45e36c status 2024-11-20 16:21:41 +03:00
Kseninia Mikhaylova e464cb1584 front 2024-11-20 16:16:50 +03:00
Kseninia Mikhaylova 06240f1708 front 2024-11-20 16:13:49 +03:00
Kseninia Mikhaylova 4f871d20f1 old ver 2024-11-20 16:00:51 +03:00
Kseninia Mikhaylova bec1968df6 add new front 2024-11-20 15:57:35 +03:00
Kseninia Mikhaylova 6cdc2d7f9a add new front 2024-11-20 15:53:24 +03:00
Kseninia Mikhaylova 768bea9967 test front 2024-11-20 14:53:43 +03:00
Kseninia Mikhaylova 798fc92f21 format 2024-11-20 14:26:08 +03:00
Kseninia Mikhaylova f3f45d25f9 start 2024-11-20 14:23:42 +03:00
Kseninia Mikhaylova bab630934c all 2024-11-20 14:15:33 +03:00
Kseninia Mikhaylova a94bcdfee2 all 2024-11-20 14:14:41 +03:00
Kseninia Mikhaylova 7b1027cc95 limits 2024-11-20 14:11:46 +03:00
Kseninia Mikhaylova 07d8062d8b limit test 2024-11-20 14:04:46 +03:00
Kseninia Mikhaylova cc0eeaf0be date fix 2024-11-20 13:58:12 +03:00
13 changed files with 11681 additions and 33 deletions

View File

@ -19,4 +19,6 @@ RUN poetry install
COPY . ${WORKING_DIR} COPY . ${WORKING_DIR}
RUN cd front && npm install && npm run generate
RUN cd ..
CMD poetry run uvicorn app.main:app --host 0.0.0.0 --port 80 CMD poetry run uvicorn app.main:app --host 0.0.0.0 --port 80

View File

@ -5,14 +5,35 @@ from datetime import datetime
import locale import locale
from urllib.parse import parse_qs from urllib.parse import parse_qs
from pathlib import Path
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from app.constants import * from app.constants import *
app = FastAPI() app = FastAPI()
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
origins = [
"http://localhost",
"http://localhost:3000",
"http://localhost:3001",
# "http://192.168.100.242"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
try: try:
locale.setlocale(locale.LC_TIME, "ru_RU") locale.setlocale(locale.LC_TIME, "ru_RU")
except Exception as e: except Exception as e:
@ -20,20 +41,23 @@ except Exception as e:
# Создаем кастомный фильтр для форматирования дат # Создаем кастомный фильтр для форматирования дат
def format_datetime(value, format="%d %B %Y, %H:%M:%S"): def format_datetime(value, format="%d %B %Y"):
"""Форматирует дату в человекочитаемый вид.""" """Форматирует дату в человекочитаемый вид."""
date = datetime.fromisoformat(value) try:
return date.strftime(format) date = datetime.fromisoformat(value)
return date.strftime(format)
except Exception as e:
return value
statuses = { statuses = {
"1": "STATE_NEW", "1": "Новая",
"2": "STATE_PENDING", "2": "Ждет выполнения",
"3": "STATE_IN_PROGRESS", "3": "Выполняется",
"4": "STATE_SUPPOSEDLY_COMPLETED", "4": "Требуется контроль",
"5": "STATE_COMPLETED", "5": "Завершена",
"6": "STATE_DEFERRED", "6": "Отложена",
"7": "STATE_DECLINED", "7": "Отклонена",
} }
@ -43,6 +67,7 @@ def format_status(value):
res = statuses[value] if value in statuses else value res = statuses[value] if value in statuses else value
return res return res
# Регистрируем фильтр в Jinja2Templates # Регистрируем фильтр в Jinja2Templates
templates.env.filters["format_status"] = format_status templates.env.filters["format_status"] = format_status
templates.env.filters["format_datetime"] = format_datetime templates.env.filters["format_datetime"] = format_datetime
@ -133,6 +158,41 @@ async def tg_intgr_get(request: Request):
return {"status": "error"} return {"status": "error"}
def extract_id(params):
json_strings = params.get("PLACEMENT_OPTIONS", [])
for json_str in json_strings:
try:
data = json.loads(json_str)
if "ID" in data:
return data["ID"]
except json.JSONDecodeError:
print(f"Ошибка: Некорректная JSON-строка - {json_str}")
def get_task_by_deal(deal_id):
select = [
"ID",
"TITLE",
"RESPONSIBLE_ID",
"CREATED_DATE",
"DEADLINE",
"STATUS",
"GROUP_ID",
]
get_task_hook = f"{WEBHOOK}/tasks.task.list?{'&'.join([f'select[]={s}' for s in select])}&filter[UF_CRM_TASK]=D_{deal_id}"
task_data = requests.get(get_task_hook)
task_data_json = task_data.json()
result = task_data_json["result"]["tasks"]
while len(task_data_json["result"]["tasks"]) == 50 and len(result) < 50 * 4:
limit = (len(result) // 50) * 50
task_data = requests.get(get_task_hook + f"&start={limit}")
task_data_json = task_data.json()
result += task_data_json["result"]["tasks"]
return result
@app.post("/deal_tab") @app.post("/deal_tab")
async def deal_tab( async def deal_tab(
request: Request, request: Request,
@ -147,32 +207,12 @@ async def deal_tab(
result = parse_qs(b_str) result = parse_qs(b_str)
q = [DOMAIN, PROTOCOL, LANG, APP_SID] q = [DOMAIN, PROTOCOL, LANG, APP_SID]
def extract_id(params):
json_strings = params.get("PLACEMENT_OPTIONS", [])
for json_str in json_strings:
try:
data = json.loads(json_str)
if "ID" in data:
return data["ID"]
except json.JSONDecodeError:
print(f"Ошибка: Некорректная JSON-строка - {json_str}")
deal_id = extract_id(result) deal_id = extract_id(result)
if not deal_id: if not deal_id:
raise ("not deal id")
deal_id = 49 deal_id = 49
select = [ result = get_task_by_deal(deal_id)
"ID",
"TITLE",
"RESPONSIBLE_ID",
"CREATED_DATE",
"DEADLINE",
"STATUS",
"GROUP_ID",
]
get_task_hook = f"{WEBHOOK}/tasks.task.list?{'&'.join([f'select[]={s}' for s in select])}&filter[UF_CRM_TASK]=D_{deal_id}"
task_data = requests.get(get_task_hook)
task_data_json = task_data.json()
# logger.info(task_data_json["result"]["tasks"]) # logger.info(task_data_json["result"]["tasks"])
parts = WEBHOOK.split("/") parts = WEBHOOK.split("/")
@ -180,10 +220,58 @@ async def deal_tab(
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, request=request,
name="deal_tab.html", name="deal_tab.html",
context={"tasks": task_data_json["result"]["tasks"], "domain": domain}, context={"tasks": result, "domain": domain},
) )
return {"status": "success", "result": task_data_json} return {"status": "success", "result": task_data_json}
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
return {"status": "error"} return {"status": "error"}
@app.post("/widget/deal_tab/{deal_id}")
async def deal_tab(request: Request, deal_id):
try:
result = get_task_by_deal(deal_id)
# logger.info(task_data_json["result"]["tasks"])
parts = WEBHOOK.split("/")
domain = f"https://{parts[2]}"
return {
"status": "success",
"result": result,
"format": {"domain": domain, "statuses": statuses},
}
except Exception as e:
logger.error(e)
return {"status": "error"}
app.mount("/widget", StaticFiles(directory="front/dist", html=True), name="static")
NUXT_DIST = Path("front/dist")
@app.post("/widget")
async def handle_widget_post(request: Request):
# Обработка данных POST-запроса
# data = await request.json()
# Загружаем сгенерированный HTML (например, index.html)
body = await request.body()
b_str = body.decode()
result = parse_qs(b_str)
deal_id = extract_id(result)
if not deal_id:
raise ("not deal id")
deal_id = 49
logger.info(result)
html_file = NUXT_DIST / "deal/index.html"
if html_file.exists():
html_content = html_file.read_text()
html_content = html_content.replace("data_deal_id:0", f"data_deal_id:{deal_id}")
return HTMLResponse(content=html_content, status_code=200)
return {"error": "Nuxt.js page not found"}, 404

24
front/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

75
front/README.md Normal file
View File

@ -0,0 +1,75 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

3
front/app.vue Normal file
View File

@ -0,0 +1,3 @@
<template>
<NuxtPage/>
</template>

23
front/nuxt.config.ts Normal file
View File

@ -0,0 +1,23 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
devtools: { enabled: true },
app: {
baseURL: '/widget/', // Базовый путь
// buildAssetsDir: '/widget/_nuxt/', // Путь для статических файлов
},
runtimeConfig: {
public: {
apiBase: process.env.NODE_ENV == 'development' ? 'http://localhost:8000' : '',
data_deal_id: 0
}
},
colorMode: {
preference: 'light'
},
modules: ['@nuxt/ui'],
})

11296
front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

18
front/package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/ui": "^2.19.2",
"nuxt": "^3.14.1592",
"vue": "latest",
"vue-router": "latest"
}
}

111
front/pages/deal.vue Normal file
View File

@ -0,0 +1,111 @@
<script setup lang="ts">
const route = useRoute()
const config = useRuntimeConfig()
const data = ref()
const formatter = ref()
export interface Data {
status: string;
result: Result[];
format: {
domain: string;
statuses: { [key: string]: string };
}
}
export interface Result {
id: string;
title: string;
responsibleId: string;
createdDate: Date;
deadline: Date;
status: string;
groupId: string;
group: any[] | GroupClass;
responsible: string;
subStatus: string;
}
export interface GroupClass {
id: string;
name: string;
opened: boolean;
membersCount: number;
image: string;
additionalData: any[];
}
const loadData = async (deal_id: number) => {
const res = await $fetch<Data>(`${config.public.apiBase}/widget/deal_tab/${deal_id}`, { method: 'POST' })
if (res.status == 'success') {
data.value = res.result
formatter.value = res.format
}
}
const columns = [{
key: 'id',
label: 'ID',
sortable: true
}, {
key: 'title',
label: 'Название'
}, {
key: 'status',
label: 'Статус',
sortable: true
}, {
key: 'responsible',
label: 'Исполнитель',
}, {
key: 'createdDate',
label: 'Создана',
sortable: true
}, {
key: 'deadline',
label: 'Крайний срок',
sortable: true
}]
onMounted(() => {
const deal_id = config.public.data_deal_id
if (!deal_id) return
loadData(deal_id)
})
</script>
<template>
<UTable :rows="data" :columns="columns">
<template #responsible-data="{ row }">
{{ row.responsible.name }}
</template>
<template #deadline-data="{ row }">
{{ new Date(Date.parse(row.deadline)).toLocaleDateString('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
}) }}
</template>
<template #createdDate-data="{ row }">
{{ new Date(Date.parse(row.createdDate)).toLocaleDateString('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric',
}) }}
</template>
<template #status-data="{ row }">
{{ formatter.statuses[row.status] }}
</template>
<template #title-data="{ row }">
<div class="max-w-screen-md break-words whitespace-break-spaces">
<a :href="`${formatter.domain}/workgroups/group/${row.groupId}/tasks/task/view/${row.id}/`"
target="_blank" class="text-primary-600 underline hover:no-underline">
{{ row.title }}
</a>
</div>
</template>
</UTable>
</template>

BIN
front/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

1
front/public/robots.txt Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

4
front/tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}