diff --git a/package.json b/package.json index 4afd77e..f85aa99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "no-copy-frontend", - "version": "0.80.0", + "version": "0.81.0", "private": true, "scripts": { "dev": "next dev -p 2999", diff --git a/src/app/actions/definitions.ts b/src/app/actions/definitions.ts index 77924dd..c05d2ff 100644 --- a/src/app/actions/definitions.ts +++ b/src/app/actions/definitions.ts @@ -92,6 +92,18 @@ export const createComplaintSchema = z .trim(), }) +export const createCaseSchema = z + .object({ + textArea: z + .string() + .min(6, { error: 'text-area-error-fill-complaint-text' }) + .trim(), + caseTitle: z + .string() + .min(6, { error: 'text-area-error-fill-complaint-text' }) + .trim(), + }) + export const localDevelopmentUrl = process.env.DEV_URL ? process.env.DEV_URL : 'http://localhost'; export const API_BASE_URL = process.env.NODE_ENV === 'development' diff --git a/src/app/actions/violationActions.ts b/src/app/actions/violationActions.ts index f32d7d8..5110136 100644 --- a/src/app/actions/violationActions.ts +++ b/src/app/actions/violationActions.ts @@ -1,7 +1,7 @@ 'use server' import { getSessionData } from '@/app/actions/session'; -import { API_BASE_URL, createComplaintSchema } from '@/app/actions/definitions'; +import { API_BASE_URL, createComplaintSchema, createCaseSchema } from '@/app/actions/definitions'; export async function getViolationSearchStatus() { const token = await getSessionData('token'); @@ -440,4 +440,176 @@ export async function updateMatchStatus(id: number, status: MatchStatus) { } catch (error) { return false } +} + +export async function fetchCaseInfo(id: number) { + const token = await getSessionData('token'); + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + /* body: JSON.stringify({ + version: 1, + msg_id: 30017, + message_body: { + action: 'get_byId', + token: token, + id: id + } + }), */ + body: JSON.stringify({ + version: 1, + msg_id: 30017, + message_body: { + action: 'get_all', + token: token, + pageSize: 10, + pageNumber: 0, + sortBy:"createdAt", + sortDir: "desc", + violation_id: id + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + + if (parsed?.message_code === 0) { + return { + body: parsed?.message_body + } + } else if (parsed?.message_code !== 0 && parsed?.message_desc === 'Law case not found') { + return { + body: null + } + } else { + throw parsed; + } + } else { + throw (`${response.status}`); + } + } catch (error) { + return { + errorMessage: 'error' + } + } +} + +interface caseBody { + previousText: string, + previousCaseTitle: string, + violationID: string | null, + success: boolean, + errorMessage?: Record | null +} + +export async function createCase( + state: caseBody | undefined, + formData: FormData +): Promise { + const email = await getSessionData('email'); + const token = await getSessionData('token'); + const violationId = formData.get('violationId') as string || ''; + const textArea = formData.get('note') as string || ''; + const caseTitle = formData.get('caseTitle') as string || ''; + const amount = formData.get('amount') as string || ''; + + const validatedFields = createCaseSchema.safeParse({ + textArea, + caseTitle + }); + + if (!validatedFields.success) { + const errors: Record = {} + JSON.parse(validatedFields.error.message).forEach((obj: { + message: string, + path: string + }) => { + if (errors[obj.path[0]]) { + errors[obj.path[0]] = `${errors[obj.path[0]]} ${obj.message}`; + } else { + errors[obj.path[0]] = obj.message; + } + }); + + return { + violationID: violationId, + previousText: textArea, + previousCaseTitle: caseTitle, + success: false, + errorMessage: errors + } + } + + try { + const response = await fetch(`${API_BASE_URL}/api/v1/data`, { + method: 'POST', + body: JSON.stringify({ + version: 1, + msg_id: 30017, + message_body: { + action: 'create', + violation_id: violationId, + description: textArea, + email: email, + token: token, + name: caseTitle, + priority: 'HIGH', + amount: Number(amount) + } + }), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (response.ok) { + let parsed = await response.json(); + + if (parsed?.message_code === 0) { + return { + violationID: violationId, + previousCaseTitle: caseTitle, + previousText: textArea, + success: true + }; + } else { + throw parsed; + } + } else { + throw (`${response.status}`); + } + } catch (error) { + const errors: Record = {} + + if (error && typeof error === 'object' && 'message_code' in error) { + const err = error as { message_code: number }; + if (err.message_code === 2) { + errors['server'] = 'the-complaint-has-not-been-registered' + + return { + violationID: violationId, + previousText: textArea, + previousCaseTitle: caseTitle, + success: false, + errorMessage: errors + } + } + } + errors['server'] = 'error-save'; + + return { + violationID: violationId, + previousText: textArea, + previousCaseTitle: caseTitle, + success: false, + errorMessage: errors + } + } } \ No newline at end of file diff --git a/src/app/styles/pages-styles.scss b/src/app/styles/pages-styles.scss index 40395be..42a79b7 100644 --- a/src/app/styles/pages-styles.scss +++ b/src/app/styles/pages-styles.scss @@ -4764,6 +4764,42 @@ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); } } + + .note-form-flex { + display: flex; + gap: 10px; + margin-bottom: 10px; + } + + .note-case-input { + border: 1px solid v.$border-color-1; + border-radius: 8px; + box-sizing: border-box; + background: #f8fafc; + padding: 5px; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + &:focus { + outline: none; + border-color: #6366f1; + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); + } + } + + .note-case-label { + font-weight: 600; + } + + .note-form-group { + input { + display: block; + } + } } .popular-questions { diff --git a/src/app/ui/dashboard/new/dashboard-user-violations.tsx b/src/app/ui/dashboard/new/dashboard-user-violations.tsx index a8288f0..a930c5b 100644 --- a/src/app/ui/dashboard/new/dashboard-user-violations.tsx +++ b/src/app/ui/dashboard/new/dashboard-user-violations.tsx @@ -59,7 +59,7 @@ export default function DashboardUserViolations() { className="section-add-file btn view-all-link" > - {t('add')} + {t('all')} diff --git a/src/app/ui/file-page/violation-table/case-complaint.tsx b/src/app/ui/file-page/violation-table/case-complaint.tsx index 53590e6..d787a74 100644 --- a/src/app/ui/file-page/violation-table/case-complaint.tsx +++ b/src/app/ui/file-page/violation-table/case-complaint.tsx @@ -6,6 +6,7 @@ import { useTranslations } from 'next-intl'; import { createComplaint, fetchComplainInfo } from '@/app/actions/violationActions'; import { toast } from 'sonner'; import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { MatchStatus } from '@/app/actions/violationActions'; interface complaintInfo { id: number, @@ -18,7 +19,10 @@ interface complaintInfo { not_moderated: boolean } -export default function CaseComplaint({ selectedViolation }: { selectedViolation: ViolationFileDetail }) { +const ENABLED_STATUSES = ['COMPLAINT_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK']; +type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise; + +export default function CaseComplaint({ selectedViolation, updateStatusHandler }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler }) { const t = useTranslations('Global'); const queryClient = useQueryClient(); @@ -38,26 +42,26 @@ export default function CaseComplaint({ selectedViolation }: { selectedViolation return data } }, - enabled: localViolation.status !== 'NEW' && - localViolation.status !== 'CREATED' && - localViolation.status !== 'SHOWED', + enabled: ENABLED_STATUSES.includes(localViolation.status), }); useEffect(() => { if (state?.success) { toast.success(t('the-complaint-has-been-registered')); + const newStatus = selectedViolation.status === 'LEGAL_IN_WORK' + ? 'COMPLAINT_AND_LEGAL_IN_WORK' + : 'COMPLAINT_IN_WORK'; + setLocalViolation(prev => ({ ...prev, - status: 'COMPLAINT_IN_WORK' + status: newStatus })); - queryClient.invalidateQueries({ queryKey: ['fileViolations'] }); - queryClient.invalidateQueries({ queryKey: ['complainInfo', localViolation.id] }); + updateStatusHandler(selectedViolation.id, newStatus); setShowCreateCase(false); } else if (state && !state.success) { - console.log('state.errorMessage'); console.log(state.errorMessage); if (state.errorMessage?.server) { toast.warning(t(state.errorMessage.server)); @@ -65,13 +69,13 @@ export default function CaseComplaint({ selectedViolation }: { selectedViolation toast.warning(t('the-complaint-has-not-been-registered')); } } - }, [state, queryClient, localViolation.id]); + }, [state, updateStatusHandler, selectedViolation.id]); function toggleForm() { setShowCreateCase(!showCreateCase); } - if (localViolation.status === 'NEW' || localViolation.status === 'CREATED' || localViolation.status === 'SHOWED') { + if (!ENABLED_STATUSES.includes(localViolation.status)) { return (
{!showCreateCase ? ( diff --git a/src/app/ui/file-page/violation-table/case-violation.tsx b/src/app/ui/file-page/violation-table/case-violation.tsx index 8a4fe9a..3fb8704 100644 --- a/src/app/ui/file-page/violation-table/case-violation.tsx +++ b/src/app/ui/file-page/violation-table/case-violation.tsx @@ -1,14 +1,255 @@ +'use client' + import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations'; +import { useEffect, useState, useActionState } from 'react'; +import { fetchCaseInfo, MatchStatus, createCase } from '@/app/actions/violationActions'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useTranslations } from 'next-intl'; +import { toast } from 'sonner'; +import { formatDate, formatDateTime } from '@/app/lib/formatDate'; -export default function CaseViolation({ selectedViolation }: { selectedViolation: ViolationFileDetail }) { +interface CaseInfo { + id: number; + name: string; + description: string; + amount: number; + priority: string; + type: string; + lawyer: string | null; + createdAt: string; + updatedAt: string; + pageNumber: number; + pageSize: number; + totalElements: number; + totalPages: number; + violationId: number; + content: string | null; +} - return ( -
- -
- ) +const ENABLED_STATUSES = ['LEGAL_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK']; +type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise; + +export default function CaseViolation({ selectedViolation, updateStatusHandler }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler }) { + const t = useTranslations('Global'); + const queryClient = useQueryClient(); + const [showCreateCase, setShowCreateCase] = useState(false); + const [localViolation, setLocalViolation] = useState(selectedViolation); + const [state, formAction, isPending] = useActionState(createCase, undefined); + + const { data: caseInfo, isLoading: isLoadingCase } = useQuery({ + queryKey: ['caseInfo', localViolation.id], + queryFn: () => fetchCaseInfo(localViolation.id), + select: (data) => { + if (data) { + return data + } + }, + enabled: ENABLED_STATUSES.includes(localViolation.status) + }); + + useEffect(() => { + if (state?.success) { + toast.success(t('the-complaint-has-been-registered')); + + const newStatus = selectedViolation.status === 'COMPLAINT_IN_WORK' + ? 'COMPLAINT_AND_LEGAL_IN_WORK' + : 'LEGAL_IN_WORK'; + + setLocalViolation(prev => ({ + ...prev, + status: newStatus + })); + + updateStatusHandler(selectedViolation.id, newStatus); + + setShowCreateCase(false); + } else if (state && !state.success) { + console.log(state.errorMessage); + if (state.errorMessage?.server) { + toast.warning(t(state.errorMessage.server)); + } else { + toast.warning(t('the-complaint-has-not-been-registered')); + } + } + }, [state, updateStatusHandler, selectedViolation.id]); + + function toggleForm() { + setShowCreateCase(!showCreateCase); + } + + if (!ENABLED_STATUSES.includes(localViolation.status)) { + return ( +
+ {!showCreateCase ? ( + + ) : ( +
+ + +
+
+ +
+
+ + + {state?.errorMessage?.caseTitle && ( +

+ { + state?.errorMessage?.caseTitle.split('&').map((e, index) => { + return ( + + {t(e)} +
+
+ ) + }) + } +

+ )} +
+
+ + + {state?.errorMessage?.amount && ( +

+ { + state?.errorMessage?.amount.split('&').map((e, index) => { + return ( + + {t(e)} +
+
+ ) + }) + } +

+ )} +
+
+ +
+ + {state?.errorMessage?.textArea && ( +

+ { + state?.errorMessage?.textArea.split('&').map((e, index) => { + return ( + + {t(e)} +
+
+ ) + }) + } +

+ )} +
+
+ +
+
+
+
+ )} +
+ ); + } else { + if (isLoadingCase) { + return ( +
+
{t('loading')}...
+
+ ); + } + + return ( +
+ {caseInfo?.body?.content ? ( +
+ {caseInfo.body.content.map((item: CaseInfo) => { + return ( +
+

+ {t('complaint-Information')} +

+
+

ID: {item.id}

+

{t('name')}: {item.name}

+

{t('description')}: {item.description}

+

{t('amount')}: {item.amount}

+

{t('priority')}: {item.priority}

+

{t('type')}: {item.type}

+

{t('lawyer')}: {item.lawyer || t('not-assigned')}

+

{t('violation-id')}: {item.violationId}

+

{t('date-of-creation')}: {item.createdAt ? `${formatDate(item.createdAt)}: ${formatDateTime(item.createdAt)}` : '---'}

+

{t('date-of-update')}: {item.createdAt ? `${formatDate(item.createdAt)}: ${formatDateTime(item.createdAt)}` : '---'}

+ {item.content &&

{t('content')}: {item.content}

} +
+
+ ); + })} +
+ ) : ( +
+ {t('no-information-about-the-complaint')} +
+ )} +
+ ); + } } \ No newline at end of file diff --git a/src/app/ui/file-page/violation-table/file-page-violation-info.tsx b/src/app/ui/file-page/violation-table/file-page-violation-info.tsx index be6629f..e2b34d4 100644 --- a/src/app/ui/file-page/violation-table/file-page-violation-info.tsx +++ b/src/app/ui/file-page/violation-table/file-page-violation-info.tsx @@ -37,6 +37,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV toast.success('Статус обновлен'); } queryClient.invalidateQueries({ queryKey: ['fileViolations'] }); + queryClient.invalidateQueries({ queryKey: ['complainInfo', id] }); return true; } else { setLocalViolation(selectedViolation); @@ -153,8 +154,16 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
- - + +
diff --git a/src/app/ui/marking-page/new/protection-statistic.tsx b/src/app/ui/marking-page/new/protection-statistic.tsx index 8eb1d78..8c60f58 100644 --- a/src/app/ui/marking-page/new/protection-statistic.tsx +++ b/src/app/ui/marking-page/new/protection-statistic.tsx @@ -3,12 +3,17 @@ import { useTranslations } from 'next-intl'; import { convertBytes } from '@/app/lib/convertBytes'; import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo'; +import { useEffect } from 'react'; export default function ProtectionStatistic({ fileType }: { fileType: string }) { const t = useTranslations('Global'); const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo(); + useEffect(() => { + console.log(filesInfo); + }, [filesInfo]) + const getFileTypeData = (fileType: string) => { switch (fileType) { case 'image': @@ -35,7 +40,9 @@ export default function ProtectionStatistic({ fileType }: { fileType: string })
{t(`${fileType}-protected`)}
-
0
+
+ {currentData?.check ?? 0} +
{t(`${fileType}-checked`)}
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index b77fff1..3405c52 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -402,7 +402,12 @@ "the-debit-occurs-1-day-before-the-end-of-the-subscription": "The debit occurs 1 day before the end of the subscription.", "you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time": "You can unlink your card and cancel auto-renewal at any time.", "delete-a-bank-card": "Delete a bank card", - "no-new-notifications": "There are no new notifications" + "no-new-notifications": "There are no new notifications", + "description": "Description", + "priority": "Priority", + "type": "Type", + "lawyer": "Lawyer", + "not-assigned": "Not assigned" }, "Login-register-form": { "and": "and", diff --git a/src/i18n/messages/ru.json b/src/i18n/messages/ru.json index 4cfd3aa..e5c0de6 100644 --- a/src/i18n/messages/ru.json +++ b/src/i18n/messages/ru.json @@ -402,7 +402,12 @@ "the-debit-occurs-1-day-before-the-end-of-the-subscription": "Списание происходит за 1 день до окончания подписки.", "you-can-unlink-your-card-and-cancel-auto-renewal-at-any-time": "Вы можете отвязать карту и отменить автопродление в любой момент.", "delete-a-bank-card": "Удалить банковскую карту", - "no-new-notifications": "Новых уведомлений нет" + "no-new-notifications": "Новых уведомлений нет", + "description": "Описание", + "priority": "Приоритет", + "type": "Тип", + "lawyer": "Адвокат", + "not-assigned": "Не назначено" }, "Login-register-form": { "and": "и",