From b115404b1f929851b381f06ffce3c289ee38c559 Mon Sep 17 00:00:00 2001 From: Zhakhangir Anuarbek Date: Sun, 17 Aug 2025 19:09:49 +0500 Subject: [PATCH] Refactor exhibition and fight application pages to separate pending and approved applications - Split applications into pending and approved states for better management. - Implemented tab navigation for switching between pending and approved applications. - Updated fetching logic to retrieve pending applications excluding rejected ones. - Enhanced application rendering with a dedicated render function for application cards. - Adjusted approval and rejection logic to update the respective application lists. - Improved user feedback with appropriate messages for empty states in both tabs. --- src/pages/approve/exhibition.tsx | 442 ++++++++++++++++----------- src/pages/approve/fight.tsx | 508 ++++++++++++++++++------------- 2 files changed, 559 insertions(+), 391 deletions(-) diff --git a/src/pages/approve/exhibition.tsx b/src/pages/approve/exhibition.tsx index e90761b..b996fd3 100644 --- a/src/pages/approve/exhibition.tsx +++ b/src/pages/approve/exhibition.tsx @@ -27,13 +27,19 @@ interface Application { } function ExhibitionApplicationsPage() { - const [applications, setApplications] = useState([]); + const [pendingApplications, setPendingApplications] = useState( + [] + ); + const [approvedApplications, setApprovedApplications] = useState< + Application[] + >([]); const [selectedImage, setSelectedImage] = useState(null); const [loading, setLoading] = useState(true); const [processingId, setProcessingId] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [credentials, setCredentials] = useState({ email: "", password: "" }); const [authError, setAuthError] = useState(""); + const [activeTab, setActiveTab] = useState<"pending" | "approved">("pending"); useEffect(() => { // Проверяем, авторизован ли пользователь @@ -63,30 +69,41 @@ function ExhibitionApplicationsPage() { const handleLogout = () => { pb.authStore.clear(); setIsAuthenticated(false); - setApplications([]); + setPendingApplications([]); + setApprovedApplications([]); }; const fetchApplications = async () => { try { - const records = await pb.collection("forms").getFullList({ - filter: 'type = "exhibition" && approved != true', + // Fetch pending applications - exclude both approved and rejected + const pendingRecords = await pb.collection("forms").getFullList({ + filter: + 'type = "exhibition" && approved != true && status != "rejected"', sort: "-created", }); - const formattedApplications = records.map((record: any) => ({ - id: record.id, - data: - typeof record.data === "string" - ? JSON.parse(record.data) - : record.data, - images: record.images || [], - type: record.type, - status: record.status || "pending", - approved: record.approved || false, - created: record.created, - })); + // Fetch approved applications + const approvedRecords = await pb.collection("forms").getFullList({ + filter: 'type = "exhibition" && approved = true', + sort: "-created", + }); - setApplications(formattedApplications); + const formatApplications = (records: any[]) => + records.map((record: any) => ({ + id: record.id, + data: + typeof record.data === "string" + ? JSON.parse(record.data) + : record.data, + images: record.images || [], + type: record.type, + status: record.status || "pending", + approved: record.approved || false, + created: record.created, + })); + + setPendingApplications(formatApplications(pendingRecords)); + setApprovedApplications(formatApplications(approvedRecords)); } catch (error) { console.error("Error fetching applications:", error); @@ -117,11 +134,20 @@ function ExhibitionApplicationsPage() { const handleApprove = async (id: string) => { setProcessingId(id); try { - await pb.collection("forms").update(id, { + await pb.collection("forms").update(id, { status: "approved", - approved: true + approved: true, }); - setApplications((prev) => prev.filter((app) => app.id !== id)); + + // Move from pending to approved + const approvedApp = pendingApplications.find((app) => app.id === id); + if (approvedApp) { + approvedApp.status = "approved"; + approvedApp.approved = true; + setApprovedApplications((prev) => [approvedApp, ...prev]); + setPendingApplications((prev) => prev.filter((app) => app.id !== id)); + } + alert("Заявка одобрена!"); } catch (error) { console.error("Error approving application:", error); @@ -134,11 +160,11 @@ function ExhibitionApplicationsPage() { const handleReject = async (id: string) => { setProcessingId(id); try { - await pb.collection("forms").update(id, { + await pb.collection("forms").update(id, { status: "rejected", - approved: false + approved: false, }); - setApplications((prev) => prev.filter((app) => app.id !== id)); + setPendingApplications((prev) => prev.filter((app) => app.id !== id)); alert("Заявка отклонена!"); } catch (error) { console.error("Error rejecting application:", error); @@ -159,6 +185,148 @@ function ExhibitionApplicationsPage() { return url; }; + const renderApplicationCard = ( + application: Application, + showActions = true + ) => ( +
+
+
+ {/* Личная информация */} +
+

+ Информация об участнике +

+
+
+ Имя: + {application.data.name} +
+
+ + Телефон: + + {application.data.phone} +
+
+ Email: + {application.data.email} +
+
+
+ + {/* Информация об автомобиле для выставки */} +
+

+ Автомобиль для выставки +

+
+
+ Марка: + + {application.data.carBrand} + +
+
+ + Модель: + + + {application.data.carModel} + +
+ {application.data.description && ( +
+ + Описание: + + + {application.data.description} + +
+ )} +
+
+
+ + {/* Фотографии */} + {application.images.length > 0 && ( +
+

+ Фотографии для выставки ({application.images.length}) +

+
+ {application.images.map((image, index) => { + const imageUrl = getImageUrl(application, image); + return ( +
setSelectedImage(imageUrl)} + > + {`Фото +
+
+ Увеличить +
+
+
+ ); + })} +
+
+ )} + + {/* Дата подачи заявки и статус */} +
+
+ Заявка подана:{" "} + {new Date(application.created).toLocaleString("ru-RU")} +
+ {application.approved && ( +
+ + Одобрено + +
+ )} +
+
+ + {/* Кнопки действий */} + {showActions && ( +
+ + +
+ )} +
+ ); + // Форма авторизации if (!isAuthenticated) { return ( @@ -284,13 +452,77 @@ function ExhibitionApplicationsPage() { + {/* Tabs */} +
+
+ + +
+
+ {/* Content */}
- {applications.length === 0 ? ( + {activeTab === "pending" ? ( + pendingApplications.length === 0 ? ( +
+
+ + + +
+

+ Нет ожидающих заявок +

+

+ Все заявки обработаны +

+
+ ) : ( +
+ {pendingApplications.map((application) => + renderApplicationCard(application, true) + )} +
+ ) + ) : approvedApplications.length === 0 ? (
-
+

- Нет заявок + Нет одобренных заявок

- Пока нет заявок на выставку для модерации + Пока нет одобренных заявок

) : (
- {applications.map((application) => ( -
-
-
- {/* Личная информация */} -
-

- Информация об участнике -

-
-
- - Имя: - - - {application.data.name} - -
-
- - Телефон: - - - {application.data.phone} - -
-
- - Email: - - - {application.data.email} - -
-
-
- - {/* Информация об автомобиле для выставки */} -
-

- Автомобиль для выставки -

-
-
- - Марка: - - - {application.data.carBrand} - -
-
- - Модель: - - - {application.data.carModel} - -
- {application.data.description && ( -
- - Описание: - - - {application.data.description} - -
- )} -
-
-
- - {/* Фотографии */} - {application.images.length > 0 && ( -
-

- Фотографии для выставки ({application.images.length}) -

-
- {application.images.map((image, index) => { - const imageUrl = getImageUrl(application, image); - return ( -
setSelectedImage(imageUrl)} - > - {`Фото -
-
- Увеличить -
-
-
- ); - })} -
-
- )} - - {/* Дата подачи заявки */} -
-
- Заявка подана:{" "} - {new Date(application.created).toLocaleString("ru-RU")} -
-
-
- - {/* Кнопки действий */} -
- - -
-
- ))} + {approvedApplications.map((application) => + renderApplicationCard(application, false) + )}
)}
diff --git a/src/pages/approve/fight.tsx b/src/pages/approve/fight.tsx index 511627c..4ed0dd2 100644 --- a/src/pages/approve/fight.tsx +++ b/src/pages/approve/fight.tsx @@ -28,18 +28,24 @@ interface Application { images: string[]; type: string; status?: string; - approved?: boolean; // Add this new field + approved?: boolean; created: string; } function FightApplicationsPage() { - const [applications, setApplications] = useState([]); + const [pendingApplications, setPendingApplications] = useState( + [] + ); + const [approvedApplications, setApprovedApplications] = useState< + Application[] + >([]); const [selectedImage, setSelectedImage] = useState(null); const [loading, setLoading] = useState(true); const [processingId, setProcessingId] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [credentials, setCredentials] = useState({ email: "", password: "" }); const [authError, setAuthError] = useState(""); + const [activeTab, setActiveTab] = useState<"pending" | "approved">("pending"); useEffect(() => { // Проверяем, авторизован ли пользователь @@ -69,30 +75,40 @@ function FightApplicationsPage() { const handleLogout = () => { pb.authStore.clear(); setIsAuthenticated(false); - setApplications([]); + setPendingApplications([]); + setApprovedApplications([]); }; const fetchApplications = async () => { try { - const records = await pb.collection("forms").getFullList({ - filter: 'type = "fight" && approved != true', // Updated filter to use boolean field + // Fetch pending applications - exclude both approved and rejected + const pendingRecords = await pb.collection("forms").getFullList({ + filter: 'type = "fight" && approved != true && status != "rejected"', sort: "-created", }); - const formattedApplications = records.map((record: any) => ({ - id: record.id, - data: - typeof record.data === "string" - ? JSON.parse(record.data) - : record.data, - images: record.images || [], - type: record.type, - status: record.status || "pending", - approved: record.approved || false, // Include the approved field - created: record.created, - })); + // Fetch approved applications + const approvedRecords = await pb.collection("forms").getFullList({ + filter: 'type = "fight" && approved = true', + sort: "-created", + }); - setApplications(formattedApplications); + const formatApplications = (records: any[]) => + records.map((record: any) => ({ + id: record.id, + data: + typeof record.data === "string" + ? JSON.parse(record.data) + : record.data, + images: record.images || [], + type: record.type, + status: record.status || "pending", + approved: record.approved || false, + created: record.created, + })); + + setPendingApplications(formatApplications(pendingRecords)); + setApprovedApplications(formatApplications(approvedRecords)); } catch (error) { console.error("Error fetching applications:", error); @@ -127,11 +143,20 @@ function FightApplicationsPage() { const handleApprove = async (id: string) => { setProcessingId(id); try { - await pb.collection("forms").update(id, { + await pb.collection("forms").update(id, { status: "approved", - approved: true // Set the boolean field + approved: true, }); - setApplications((prev) => prev.filter((app) => app.id !== id)); + + // Move from pending to approved + const approvedApp = pendingApplications.find((app) => app.id === id); + if (approvedApp) { + approvedApp.status = "approved"; + approvedApp.approved = true; + setApprovedApplications((prev) => [approvedApp, ...prev]); + setPendingApplications((prev) => prev.filter((app) => app.id !== id)); + } + alert("Заявка одобрена!"); } catch (error) { console.error("Error approving application:", error); @@ -144,11 +169,11 @@ function FightApplicationsPage() { const handleReject = async (id: string) => { setProcessingId(id); try { - await pb.collection("forms").update(id, { + await pb.collection("forms").update(id, { status: "rejected", - approved: false // Explicitly set to false + approved: false, }); - setApplications((prev) => prev.filter((app) => app.id !== id)); + setPendingApplications((prev) => prev.filter((app) => app.id !== id)); alert("Заявка отклонена!"); } catch (error) { console.error("Error rejecting application:", error); @@ -171,6 +196,179 @@ function FightApplicationsPage() { return url; }; + const renderApplicationCard = ( + application: Application, + showActions = true + ) => ( +
+
+
+ {/* Личная информация */} +
+

+ Личная информация +

+
+
+ ФИО: + + {application.data.lastName} {application.data.firstName}{" "} + {application.data.middleName} + +
+
+ + Дата рождения: + + + {application.data.birthDate} + +
+
+ + Гражданство: + + + {application.data.citizenship} + +
+
+ + Телефон: + + {application.data.phone} +
+
+ Email: + {application.data.email} +
+
+
+ + {/* Информация об автомобиле */} +
+

+ Автомобиль +

+
+
+ Марка: + + {application.data.carBrand} + +
+
+ + Модель: + + + {application.data.carModel} + +
+
+ + Двигатель: + + {application.data.engine} +
+
+ + Мощность: + + {application.data.power} +
+ {application.data.additionalInfo && ( +
+ + Доп. информация: + + + {application.data.additionalInfo} + +
+ )} +
+
+
+ + {/* Фотографии */} + {application.images.length > 0 && ( +
+

+ Фотографии автомобиля ({application.images.length}) +

+
+ {application.images.map((image, index) => { + const imageUrl = getImageUrl(application, image); + return ( +
setSelectedImage(imageUrl)} + > + {`Фото +
+
+ Увеличить +
+
+
+ ); + })} +
+
+ )} + + {/* Дата подачи заявки и статус */} +
+
+ Заявка подана:{" "} + {new Date(application.created).toLocaleString("ru-RU")} +
+ {application.approved && ( +
+ + Одобрено + +
+ )} +
+
+ + {/* Кнопки действий */} + {showActions && ( +
+ + +
+ )} +
+ ); + // Форма авторизации if (!isAuthenticated) { return ( @@ -296,13 +494,77 @@ function FightApplicationsPage() {
+ {/* Tabs */} +
+
+ + +
+
+ {/* Content */}
- {applications.length === 0 ? ( + {activeTab === "pending" ? ( + pendingApplications.length === 0 ? ( +
+
+ + + +
+

+ Нет ожидающих заявок +

+

+ Все заявки обработаны +

+
+ ) : ( +
+ {pendingApplications.map((application) => + renderApplicationCard(application, true) + )} +
+ ) + ) : approvedApplications.length === 0 ? (
-
+

- Нет заявок + Нет одобренных заявок

- Пока нет заявок для модерации + Пока нет одобренных заявок

) : (
- {applications.map((application) => ( -
-
-
- {/* Личная информация */} -
-

- Личная информация -

-
-
- - ФИО: - - - {application.data.lastName}{" "} - {application.data.firstName}{" "} - {application.data.middleName} - -
-
- - Дата рождения: - - - {application.data.birthDate} - -
-
- - Гражданство: - - - {application.data.citizenship} - -
-
- - Телефон: - - - {application.data.phone} - -
-
- - Email: - - - {application.data.email} - -
-
-
- - {/* Информация об автомобиле */} -
-

- Автомобиль -

-
-
- - Марка: - - - {application.data.carBrand} - -
-
- - Модель: - - - {application.data.carModel} - -
-
- - Двигатель: - - - {application.data.engine} - -
-
- - Мощность: - - - {application.data.power} - -
- {application.data.additionalInfo && ( -
- - Доп. информация: - - - {application.data.additionalInfo} - -
- )} -
-
-
- - {/* Фотографии */} - {application.images.length > 0 && ( -
-

- Фотографии автомобиля ({application.images.length}) -

-
- {application.images.map((image, index) => { - const imageUrl = getImageUrl(application, image); - return ( -
setSelectedImage(imageUrl)} - > - {`Фото -
-
- Увеличить -
-
-
- ); - })} -
-
- )} - - {/* Дата подачи заявки */} -
-
- Заявка подана:{" "} - {new Date(application.created).toLocaleString("ru-RU")} -
-
-
- - {/* Кнопки действий */} -
- - -
-
- ))} + {approvedApplications.map((application) => + renderApplicationCard(application, false) + )}
)}