matches: add animation, add suspense for table, add no-image image
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -96,11 +96,11 @@ export const createCaseSchema = z
|
||||
.object({
|
||||
textArea: z
|
||||
.string()
|
||||
.min(6, { error: 'text-area-error-fill-complaint-text' })
|
||||
.min(6, { error: 'fill-text-of-the-case' })
|
||||
.trim(),
|
||||
caseTitle: z
|
||||
.string()
|
||||
.min(6, { error: 'text-area-error-fill-complaint-text' })
|
||||
.min(6, { error: 'fill-title-of-the-case' })
|
||||
.trim(),
|
||||
})
|
||||
|
||||
|
||||
@@ -4457,9 +4457,10 @@
|
||||
padding: 15px;
|
||||
height: calc(16vh - 12px);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
opacity: 0.75;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
@@ -4497,10 +4498,12 @@
|
||||
max-height: 80%;
|
||||
min-height: 50px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
height: 100% !important;
|
||||
border-radius: 8px;
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4662,6 +4665,7 @@
|
||||
&-image {
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
@@ -4727,6 +4731,7 @@
|
||||
|
||||
&-case {
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
|
||||
&:first-child {
|
||||
border-bottom: 1px solid rgb(226, 232, 240);
|
||||
@@ -4763,6 +4768,12 @@
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-form-flex {
|
||||
@@ -4789,6 +4800,10 @@
|
||||
border-color: #6366f1;
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.note-case-label {
|
||||
@@ -5389,6 +5404,7 @@
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px #0000000d;
|
||||
color: v.$p-color;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
@@ -5414,6 +5430,7 @@
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
color: v.$p-color;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -143,7 +143,14 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
||||
if (isLoadingComplain) {
|
||||
return (
|
||||
<div className="violation-info-case">
|
||||
<div>{t('loading')}...</div>
|
||||
<div
|
||||
className="loading-animation"
|
||||
>
|
||||
<div
|
||||
className="global-spinner large"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
||||
className="btn-action btn-case"
|
||||
onClick={toggleForm}
|
||||
>
|
||||
Создание дела
|
||||
{t('create-case')}
|
||||
</button>
|
||||
) : (
|
||||
<div>
|
||||
@@ -111,12 +111,13 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
||||
className="note-case-label"
|
||||
htmlFor="caseTitle"
|
||||
>
|
||||
Название дела
|
||||
{t('case-name')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="caseTitle"
|
||||
className="note-case-input"
|
||||
placeholder={t('fill-title-of-the-case') + '...'}
|
||||
/>
|
||||
{state?.errorMessage?.caseTitle && (
|
||||
<p
|
||||
@@ -142,12 +143,13 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
||||
className="note-case-label"
|
||||
htmlFor="amount"
|
||||
>
|
||||
Сумма ущерба
|
||||
{t('amount-of-damage')}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="amount"
|
||||
className="note-case-input"
|
||||
placeholder={t('enter-the-amount-of-damage') + '...'}
|
||||
/>
|
||||
{state?.errorMessage?.amount && (
|
||||
<p
|
||||
@@ -168,11 +170,14 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<section
|
||||
className="note-textarea-wrapper"
|
||||
>
|
||||
<textarea
|
||||
name="note"
|
||||
className="note-textarea"
|
||||
placeholder={`Текст дела...`}>
|
||||
placeholder={t('fill-text-of-the-case') + '...'}
|
||||
>
|
||||
</textarea>
|
||||
{state?.errorMessage?.textArea && (
|
||||
<p
|
||||
@@ -212,7 +217,15 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
||||
if (isLoadingCase) {
|
||||
return (
|
||||
<div className="violation-info-case">
|
||||
<div>{t('loading')}...</div>
|
||||
<div
|
||||
className="loading-animation"
|
||||
>
|
||||
<div
|
||||
className="global-spinner large"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { MatchStatus } from '@/app/actions/violationActions';
|
||||
import { toast } from 'sonner';
|
||||
import FilePageViolationEpmtyScreen from '@/app/ui/file-page/violation-table/file-page-violation-epmty-screen';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function FilePageViolationInfo({ selectedViolation }: { selectedViolation: ViolationFileDetail | null }) {
|
||||
const t = useTranslations('Global');
|
||||
@@ -88,7 +89,17 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
||||
<div
|
||||
className="violation-info-image"
|
||||
>
|
||||
<img src={selectedViolation?.url} alt={selectedViolation?.page_title} />
|
||||
<Image
|
||||
src={selectedViolation?.url}
|
||||
alt={selectedViolation?.page_title}
|
||||
unoptimized
|
||||
fill
|
||||
sizes="100px"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = '/images/no-image.png';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import FilePageViolationInfo from '@/app/ui/file-page/violation-table/file-page-violation-info';
|
||||
import { useFileViolations } from '@/app/hooks/react-query/useFileViolations';
|
||||
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
||||
import DropDownList from '@/app/components/DropDownList';
|
||||
import { IconArrowLeft, IconArrowRight, IconDoubleArrowLeft, IconDoubleArrowRight } from '@/app/ui/icons/icons';
|
||||
import FilePageViolationsLoadingList from '@/app/ui/file-page/violation-table/file-page-violations-loading-list';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function FilePageViolationsList({
|
||||
currentPage,
|
||||
@@ -26,12 +28,36 @@ export default function FilePageViolationsList({
|
||||
const searchParams = useSearchParams();
|
||||
const [selectedViolation, setSelectedViolation] = useState<ViolationFileDetail | null>(null);
|
||||
|
||||
const [cachedPagination, setCachedPagination] = useState<{
|
||||
total_pages: number;
|
||||
total_elements: number;
|
||||
current_page: number;
|
||||
} | null>(null);
|
||||
|
||||
const { data: fileViolations, error, isPending } = useFileViolations(fileId, currentPage, status);
|
||||
|
||||
if (!fileViolations?.violations) {
|
||||
return null;
|
||||
}
|
||||
useEffect(() => {
|
||||
if (fileViolations?.violations) {
|
||||
setCachedPagination({
|
||||
total_pages: fileViolations.total_pages,
|
||||
total_elements: fileViolations.total_elements,
|
||||
current_page: fileViolations.current_page
|
||||
});
|
||||
}
|
||||
}, [fileViolations]);
|
||||
|
||||
const paginationData = fileViolations?.violations ? fileViolations : cachedPagination;
|
||||
|
||||
if (!fileViolations?.violations) {
|
||||
return <FilePageViolationsLoadingList
|
||||
currentPage={currentPage}
|
||||
fileId={fileId}
|
||||
status={status}
|
||||
cachedTotalPages={paginationData?.total_pages}
|
||||
cachedTotalElements={paginationData?.total_elements}
|
||||
cachedCurrentPage={paginationData?.current_page}
|
||||
/>;
|
||||
}
|
||||
const handlePageChange = (page: number) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set('page', page.toString());
|
||||
@@ -105,7 +131,7 @@ export default function FilePageViolationsList({
|
||||
className="sources-list-wrapper"
|
||||
>
|
||||
<div className="sources-list">
|
||||
{fileViolations.violations.map(item => {
|
||||
{fileViolations?.violations.map(item => {
|
||||
return (
|
||||
<div
|
||||
className={`source-card ${selectedViolation?.id === item.id ? 'selected' : ''}`}
|
||||
@@ -118,7 +144,17 @@ export default function FilePageViolationsList({
|
||||
className="source-header-left"
|
||||
>
|
||||
<div className="source-image">
|
||||
<img src={item.url} alt="" />
|
||||
<Image
|
||||
src={item.url}
|
||||
alt={item.page_title}
|
||||
unoptimized
|
||||
fill
|
||||
sizes="100px"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = '/images/no-image.png';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="source-url-block"
|
||||
@@ -177,9 +213,9 @@ export default function FilePageViolationsList({
|
||||
className="sources-list-pagination"
|
||||
>
|
||||
<span>
|
||||
{t('total-violations')}: {fileViolations.total_elements} | {t('page')} {fileViolations.current_page} {t('out-of')} {fileViolations.total_pages}
|
||||
{t('total-violations')}: {fileViolations?.total_elements} | {t('page')} {fileViolations?.current_page} {t('out-of')} {fileViolations?.total_pages}
|
||||
</span>
|
||||
{fileViolations.total_pages > 1 && (
|
||||
{fileViolations?.total_pages > 1 && (
|
||||
<div className="pagination-controls">
|
||||
<button
|
||||
className="arrow"
|
||||
@@ -191,7 +227,7 @@ export default function FilePageViolationsList({
|
||||
<button
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
className="arrow"
|
||||
disabled={!fileViolations.has_previous || isPending}
|
||||
disabled={!fileViolations?.has_previous || isPending}
|
||||
>
|
||||
<IconArrowLeft />
|
||||
</button>
|
||||
@@ -215,14 +251,14 @@ export default function FilePageViolationsList({
|
||||
<button
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
className="arrow"
|
||||
disabled={!fileViolations.has_next}
|
||||
disabled={!fileViolations?.has_next}
|
||||
>
|
||||
<IconArrowRight />
|
||||
</button>
|
||||
<button
|
||||
className="arrow"
|
||||
onClick={() => handlePageChange(fileViolations.total_pages)}
|
||||
disabled={currentPage === fileViolations.total_pages || isPending}
|
||||
onClick={() => handlePageChange(fileViolations?.total_pages)}
|
||||
disabled={currentPage === fileViolations?.total_pages || isPending}
|
||||
>
|
||||
<IconDoubleArrowRight />
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link';
|
||||
import FilePageViolationInfo from '@/app/ui/file-page/violation-table/file-page-violation-info';
|
||||
import DropDownList from '@/app/components/DropDownList';
|
||||
import { IconArrowLeft, IconArrowRight, IconDoubleArrowLeft, IconDoubleArrowRight } from '@/app/ui/icons/icons';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function FilePageViolationsLoadingList({
|
||||
currentPage,
|
||||
fileId,
|
||||
status,
|
||||
cachedTotalPages,
|
||||
cachedTotalElements,
|
||||
cachedCurrentPage,
|
||||
}: {
|
||||
currentPage: number,
|
||||
fileId: string,
|
||||
status: string | undefined,
|
||||
cachedTotalPages: number | undefined,
|
||||
cachedTotalElements: number | undefined,
|
||||
cachedCurrentPage: number | undefined
|
||||
}) {
|
||||
const t = useTranslations('Global');
|
||||
const tStatus = useTranslations('Match-status');
|
||||
|
||||
const getPageNumbers = () => {
|
||||
const pages = [];
|
||||
const maxVisible = 5;
|
||||
const total = cachedTotalPages;
|
||||
|
||||
let start = Math.max(1, currentPage - Math.floor(maxVisible / 2));
|
||||
let end = Math.min(total ? total : 0, start + maxVisible - 1);
|
||||
|
||||
if (end - start + 1 < maxVisible) {
|
||||
start = Math.max(1, end - maxVisible + 1);
|
||||
}
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
return pages;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="block-wrapper">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
{t('all-file-violations')}
|
||||
</h3>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className="status-filter-select"
|
||||
>
|
||||
<DropDownList
|
||||
value={status ? tStatus(status) : t('all-statuses')}
|
||||
callBack={() => { }}
|
||||
>
|
||||
<li value=''>{t('all-statuses')}</li>
|
||||
<li value="NEW">{tStatus('NEW')}</li>
|
||||
<li value="SHOWED">{tStatus('SHOWED')}</li>
|
||||
<li value="LEGAL_IN_WORK">{tStatus('LEGAL_IN_WORK')}</li>
|
||||
<li value="COMPLAINT_IN_WORK">{tStatus('COMPLAINT_IN_WORK')}</li>
|
||||
<li value="COMPLAINT_AND_LEGAL_IN_WORK">{tStatus('COMPLAINT_AND_LEGAL_IN_WORK')}</li>
|
||||
<li value="AUTHORIZED_USE">{tStatus('AUTHORIZED_USE')}</li>
|
||||
</DropDownList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="sources-list-wrapper relative"
|
||||
>
|
||||
<div
|
||||
className="loading-animation"
|
||||
>
|
||||
<div
|
||||
className="global-spinner large"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="sources-list-pagination"
|
||||
>
|
||||
|
||||
<span>
|
||||
{t('total-violations')}: {cachedTotalElements} | {t('page')} {cachedCurrentPage} {t('out-of')} {cachedTotalPages}
|
||||
</span>
|
||||
<div className="pagination-controls">
|
||||
<button
|
||||
className="arrow"
|
||||
onClick={() => { }}
|
||||
disabled={true}
|
||||
>
|
||||
<IconDoubleArrowLeft />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { }}
|
||||
className="arrow"
|
||||
disabled={true}
|
||||
>
|
||||
<IconArrowLeft />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className="pagination-controls-pages"
|
||||
>
|
||||
{getPageNumbers().map(pageNum => (
|
||||
<button
|
||||
key={pageNum}
|
||||
onClick={() => { }}
|
||||
className={currentPage === pageNum ? 'current' : 'other'}
|
||||
disabled={true}
|
||||
>
|
||||
{pageNum}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => { }}
|
||||
className="arrow"
|
||||
disabled={true}
|
||||
>
|
||||
<IconArrowRight />
|
||||
</button>
|
||||
<button
|
||||
className="arrow"
|
||||
onClick={() => { }}
|
||||
disabled={true}
|
||||
>
|
||||
<IconDoubleArrowRight />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -407,7 +407,14 @@
|
||||
"priority": "Priority",
|
||||
"type": "Type",
|
||||
"lawyer": "Lawyer",
|
||||
"not-assigned": "Not assigned"
|
||||
"not-assigned": "Not assigned",
|
||||
"create-case": "Create case",
|
||||
"case-name": "Case name",
|
||||
"amount-of-damage": "Amount of damage",
|
||||
"text-of-the-case": "Text of the case",
|
||||
"fill-text-of-the-case": "Fill the case text",
|
||||
"fill-title-of-the-case": "Fill the case title",
|
||||
"enter-the-amount-of-damage": "Enter the amount of damage"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"and": "and",
|
||||
|
||||
@@ -407,7 +407,14 @@
|
||||
"priority": "Приоритет",
|
||||
"type": "Тип",
|
||||
"lawyer": "Адвокат",
|
||||
"not-assigned": "Не назначено"
|
||||
"not-assigned": "Не назначено",
|
||||
"create-case": "Создать дело",
|
||||
"case-name": "Название дела",
|
||||
"amount-of-damage": "Сумма ущерба",
|
||||
"text-of-the-case": "Текст дела",
|
||||
"fill-text-of-the-case": "Заполните текст дела",
|
||||
"fill-title-of-the-case": "Заполните название дела",
|
||||
"enter-the-amount-of-damage": "Введите сумму ущерба"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"and": "и",
|
||||
|
||||
Reference in New Issue
Block a user