Skip to content

Commit 611b271

Browse files
author
Victoria Ivanova
committed
create new bug; upload attachments to bugs; delete bug attachments
1 parent 48116e1 commit 611b271

File tree

18 files changed

+682
-93
lines changed

18 files changed

+682
-93
lines changed

frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!doctype html>
2-
<html lang="en">
2+
<html lang="en" class="scroll-smooth">
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"prepare": "npx simple-git-hooks",
88
"start": "npm run start-backend && npm run dev",
9-
"start-backend": "docker compose -f ../docker-compose.yml --profile back up",
9+
"start-backend": "docker compose -f ../docker-compose.yml --profile back up -d",
1010
"dev": "vite",
1111
"build": "tsc && vite build",
1212
"lint": "eslint .",

frontend/src/api/attachments/index.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1-
import { AttachmentTypes } from "@/const";
21
import { AttachmentResponse } from "./models";
32
import axios from "../axios";
43

5-
export const createBugAttachment = async (
6-
reportId: number,
7-
bugId: number,
8-
file: File,
9-
attachType: AttachmentTypes
10-
): Promise<AttachmentResponse> => {
11-
const formData = new FormData();
12-
formData.append("file", file);
13-
const { data } = await axios.post<AttachmentResponse>(
14-
`/v2/reports/${reportId}/bugs/${bugId}/attachments?attachType=${attachType}`,
15-
formData,
16-
{ headers: { "Content-Type": "multipart/form-data" } }
17-
);
18-
return data;
4+
type UploadAttachmentParameters = {
5+
reportId: number;
6+
bugId: number;
7+
attachType: number;
8+
file: File;
9+
};
10+
11+
export const uploadAttachment = async (params: UploadAttachmentParameters) => {
12+
try {
13+
const { reportId, bugId, file, attachType } = params;
14+
const formData = new FormData();
15+
formData.append("file", file);
16+
17+
const { data } = await axios.post(
18+
`/v2/reports/${reportId}/bugs/${bugId}/attachments?attachType=${attachType}`,
19+
formData,
20+
{
21+
headers: {
22+
"Content-Type": "multipart/form-data",
23+
},
24+
}
25+
);
26+
return data;
27+
} catch (error) {
28+
console.error("Ошибка при загрузке файла:", error);
29+
throw new Error("Не удалось загрузить файл");
30+
}
1931
};
2032

2133
export const deleteBugAttachment = async (

frontend/src/api/axios.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import {
44
convertObjectToCamel,
55
convertObjectToSnake,
66
} from "@/utils/convertCases";
7-
8-
const API_URL = window.env?.API_URL || import.meta.env.VITE_BASE_URL;
7+
import { API_URL } from "@/const";
98

109
const instance = axios.create({
1110
baseURL: API_URL,
@@ -43,8 +42,8 @@ instance.interceptors.response.use((response) => {
4342

4443
// Интерцептор запроса: преобразуем camelCase → snake_case
4544
instance.interceptors.request.use((config) => {
46-
if (config.headers["Content-Type"] !== "multipart/form-data") {
47-
config.data && (config.data = convertObjectToSnake(config.data));
45+
if (config.headers["Content-Type"] !== "multipart/form-data" && config.data) {
46+
config.data = convertObjectToSnake(config.data);
4847
}
4948

5049
if (signalRConnectionId) {

frontend/src/apiObsolete/axios.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import {
44
convertObjectToCamel,
55
convertObjectToSnake,
66
} from "@/utils/convertCases";
7-
8-
const API_URL = window.env?.API_URL || import.meta.env.VITE_BASE_URL;
7+
import { API_URL } from "@/const";
98

109
const instance = axios.create({
1110
baseURL: API_URL,

frontend/src/components/StatusSelect/StatusSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const StatusSelect = <T,>({
2424
<div className={`dropdown dropdown-end ${className}`}>
2525
<label
2626
tabIndex={0}
27-
className="flex items-center gap-2 justify-start cursor-pointer hover:bg-base-200 p-2 rounded"
27+
className="flex items-center gap-2 justify-start cursor-pointer hover:bg-base-200 p-1 rounded"
2828
>
2929
{currentOption?.indicator}
3030
</label>

frontend/src/const/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,10 @@ export const bugStatusMap: Record<number, StatusMeta> = {
5151
border: "border-success",
5252
},
5353
};
54+
55+
export const API_URL = window.env?.API_URL || import.meta.env.VITE_BASE_URL;
56+
57+
export enum BugResultTypes {
58+
RECEIVE = "receive",
59+
EXPECT = "expect",
60+
}

frontend/src/hooks/useWebSocketReportPage.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {
55
LogLevel,
66
HubConnectionState,
77
} from "@microsoft/signalr";
8-
9-
const API_URL = window.env?.API_URL || import.meta.env.VITE_BASE_URL;
8+
import { API_URL } from "@/const";
109

1110
const useWebSocketReportPage = (
1211
reportId: number,

frontend/src/pages/Report/Report.tsx

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import { useState, useEffect } from "react";
2+
import { useUnit, useStoreMap } from "effector-react";
3+
import { useParams, useNavigate } from "react-router-dom";
4+
import { formatDistanceToNow } from "date-fns";
5+
import { ru } from "date-fns/locale";
6+
17
import { useReportPageSocket } from "@/hooks/useReportPageSocket";
28
import { useSocketEvent } from "@/hooks/useSocketEvent";
39
import {
@@ -9,19 +15,12 @@ import {
915
saveTitleEvent,
1016
updateReportPathIdEvent,
1117
} from "@/store/report";
12-
import { SocketEvent } from "@/webSocketApi/models";
13-
import { formatDistanceToNow } from "date-fns";
14-
import { ru } from "date-fns/locale";
15-
import { useUnit, useStoreMap } from "effector-react";
16-
import { useEffect } from "react";
17-
import { useParams, useNavigate } from "react-router-dom";
18-
import Bug from "./components/Bug/Bug";
1918
import { $bugsData } from "@/store/bugs";
19+
import { SocketEvent } from "@/webSocketApi/models";
2020
import { BugEntity } from "@/types/bug";
21+
import { BugStatuses } from "@/const";
2122

22-
function sortBugsByDate(a: BugEntity, b: BugEntity) {
23-
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
24-
}
23+
import Bug from "./components/Bug/Bug";
2524

2625
const ReportPage = () => {
2726
const navigate = useNavigate();
@@ -30,24 +29,31 @@ const ReportPage = () => {
3029
const title = useUnit($titleStore);
3130
const creatorUserName = useUnit($creatorUserNameStore);
3231

33-
const bugs = useStoreMap({
32+
// баги существующие только на фронте
33+
const [onlyUIBugs, setOnlyUIBugs] = useState<BugEntity[]>([]);
34+
35+
const bugsFromStore = useStoreMap({
3436
store: $bugsData,
3537
keys: [reportId],
3638
fn: ({ bugs, reportBugs }, [currentReportId]) => {
3739
if (!currentReportId) return [];
3840
const bugIds = reportBugs[Number(currentReportId)] || [];
39-
return bugIds
40-
.map((id) => bugs[id])
41-
.filter(Boolean)
42-
.sort(sortBugsByDate);
41+
return bugIds.map((id) => bugs[id]).filter(Boolean);
4342
},
4443
});
4544

45+
const allBugs = [...bugsFromStore, ...onlyUIBugs];
46+
4647
useReportPageSocket();
4748
useSocketEvent(SocketEvent.ReportPatch, (patch) =>
4849
patchReportSocketEvent(patch)
4950
);
5051

52+
const handleRemoveTemporaryBug = (bugId: number) => {
53+
// удаляем с ui фронтовый баг и обновляем тем, что с бэкенда
54+
setOnlyUIBugs((prev) => prev.filter((bug) => bug.id !== bugId));
55+
};
56+
5157
// состояние страницы
5258
useEffect(() => {
5359
if (reportId) {
@@ -64,6 +70,29 @@ const ReportPage = () => {
6470
}
6571
}, [initialReport?.id, reportId, navigate]);
6672

73+
const handleAddBugClick = () => {
74+
if (!reportId) return;
75+
76+
const currentISODate = new Date().toISOString();
77+
78+
// Создаем локальный новый баг на фронте
79+
const newLocalBug: BugEntity = {
80+
id: Date.now(),
81+
receive: "",
82+
expect: "",
83+
status: BugStatuses.ACTIVE,
84+
createdAt: currentISODate,
85+
updatedAt: currentISODate,
86+
creatorUserId: "",
87+
reportId: Number(reportId),
88+
attachments: null,
89+
comments: null,
90+
isLocalOnly: true,
91+
};
92+
93+
setOnlyUIBugs((prev) => [...prev, newLocalBug]);
94+
};
95+
6796
return (
6897
<>
6998
<input
@@ -85,9 +114,21 @@ const ReportPage = () => {
85114
пользователем <strong>{creatorUserName || "Загрузка..."}</strong>
86115
</div>
87116
<div className="flex flex-col gap-2">
88-
{bugs?.map((bug) => (
89-
<Bug key={bug.id} bug={bug} />
117+
{allBugs?.map((bug: BugEntity) => (
118+
<Bug
119+
key={bug.id}
120+
bug={bug}
121+
onRemoveTemporary={
122+
bug.isLocalOnly ? handleRemoveTemporaryBug : undefined
123+
}
124+
/>
90125
))}
126+
<button
127+
className="btn btn-outline btn-primary font-normal ml-auto"
128+
onClick={handleAddBugClick}
129+
>
130+
Добавить баг
131+
</button>
91132
</div>
92133
</>
93134
);

0 commit comments

Comments
 (0)