diff --git a/src/api/metersApi.ts b/src/api/metersApi.ts new file mode 100644 index 0000000..fea645d --- /dev/null +++ b/src/api/metersApi.ts @@ -0,0 +1,37 @@ +import type { RawArea, RawMeter, PagedResponse } from './types'; + +export type { RawArea, RawMeter, PagedResponse }; + +const BASE_URL = '/api'; + +export const metersApi = { + async fetchMeters( + page: number, + pageSize: number + ): Promise> { + const params = new URLSearchParams({ + limit: String(pageSize), + offset: String((page - 1) * pageSize), + }); + const response = await fetch(`${BASE_URL}/meters/?${params}`); + if (!response.ok) + throw new Error(`Ошибка загрузки счётчиков: ${response.status}`); + return response.json() as Promise>; + }, + + async fetchAreas(ids: string[]): Promise> { + const params = new URLSearchParams(); + ids.forEach((id) => params.append('id__in', id)); + const response = await fetch(`${BASE_URL}/areas/?${params}`); + if (!response.ok) + throw new Error(`Ошибка загрузки адресов: ${response.status}`); + return response.json() as Promise>; + }, + + async deleteMeter(id: string): Promise { + const response = await fetch(`${BASE_URL}/meters/${id}/`, { + method: 'DELETE', + }); + if (!response.ok) throw new Error(`Ошибка удаления: ${response.status}`); + }, +}; diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..a21047b --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,28 @@ +export interface RawArea { + id: string; + number: number | null; + str_number_full: string | null; + str_number: string | null; + house: { id: string; address: string; fias_addrobjs: string[] } | null; +} + +export interface RawMeter { + id: string; + _type: string[]; + area: { id: string }; + is_automatic: boolean | null; + description: string | null; + installation_date: string | null; + initial_values: number[]; + serial_number: string; + model_name: string | null; + brand_name: string | null; + communication: string; +} + +export interface PagedResponse { + count: number; + next: string | null; + previous: string | null; + results: T[]; +} diff --git a/src/components/MetersTable/Pagination/Pagination.tsx b/src/components/MetersTable/Pagination/Pagination.tsx index af50300..4679dba 100644 --- a/src/components/MetersTable/Pagination/Pagination.tsx +++ b/src/components/MetersTable/Pagination/Pagination.tsx @@ -9,12 +9,50 @@ interface PaginationProps { onPageChange: (page: number) => void; } +interface PageButtonProps { + page: number; + isActive: boolean; + disabled: boolean; + onPageChange: (page: number) => void; +} + interface GapPopupProps { gap: Gap; disabled: boolean; onSelect: (page: number) => void; } +interface GapItemProps { + gap: Gap; + isOpen: boolean; + disabled: boolean; + onToggle: () => void; + onSelect: (page: number) => void; +} + +function PageButton({ + page, + isActive, + disabled, + onPageChange, +}: PageButtonProps) { + const className = `pagination__btn${isActive ? ' pagination__btn--active' : ''}`; + + return ( +
  • + +
  • + ); +} + function GapPopup({ gap, disabled, onSelect }: GapPopupProps) { return (
    + + + {isOpen && ( + <> +
    + + + )} + + ); +} + export function Pagination({ current, total, @@ -50,58 +113,38 @@ export function Pagination({ const pages = buildPages(current, total); + function handleGapToggle(idx: number) { + setOpenGapIdx(openGapIdx === idx ? null : idx); + } + + function handleGapSelect(page: number) { + onPageChange(page); + setOpenGapIdx(null); + } + return ( ); diff --git a/src/components/MetersTable/Pagination/buildPages.ts b/src/components/MetersTable/Pagination/buildPages.ts index 9d85fe0..971db6c 100644 --- a/src/components/MetersTable/Pagination/buildPages.ts +++ b/src/components/MetersTable/Pagination/buildPages.ts @@ -1,13 +1,14 @@ export type Gap = { type: 'gap'; pages: number[] }; export type PageItem = number | Gap; +const WINDOW = 2; + export const range = (from: number, to: number): number[] => Array.from({ length: to - from + 1 }, (_, i) => from + i); export function buildPages(current: number, total: number): PageItem[] { if (total <= 1) return []; - const WINDOW = 2; const visible = new Set([ 1, total, diff --git a/src/components/MetersTable/TableRow/TableRow.tsx b/src/components/MetersTable/TableRow/TableRow.tsx index 56c5aff..a366322 100644 --- a/src/components/MetersTable/TableRow/TableRow.tsx +++ b/src/components/MetersTable/TableRow/TableRow.tsx @@ -1,14 +1,12 @@ import { observer } from 'mobx-react-lite'; -import type { Instance } from 'mobx-state-tree'; import { metersStore } from '@/stores/MetersStore'; +import type { MeterType } from '@/stores/MetersStore'; import { MeterTypeIcon } from '@/components/MetersTable/icons/MeterTypeIcon'; import TrashIcon from '@/assets/TrashIcon.svg?react'; import './TableRow.css'; -type MeterInstance = Instance[number]; - interface TableRowProps { - meter: MeterInstance; + meter: MeterType; rowNum: number; } diff --git a/src/stores/MetersStore.ts b/src/stores/MetersStore.ts index 11c39c9..155fed5 100644 --- a/src/stores/MetersStore.ts +++ b/src/stores/MetersStore.ts @@ -1,37 +1,9 @@ import { types, flow, cast } from 'mobx-state-tree'; import type { Instance } from 'mobx-state-tree'; +import { metersApi } from '../api/metersApi'; +import type { RawArea, RawMeter, PagedResponse } from '../api/types'; const PAGE_SIZE = 20; -const BASE_URL = '/api'; - -interface RawArea { - id: string; - number: number | null; - str_number_full: string | null; - str_number: string | null; - house: { id: string; address: string; fias_addrobjs: string[] } | null; -} - -interface RawMeter { - id: string; - _type: string[]; - area: { id: string }; - is_automatic: boolean | null; - description: string | null; - installation_date: string | null; - initial_values: number[]; - serial_number: string; - model_name: string | null; - brand_name: string | null; - communication: string; -} - -interface PagedResponse { - count: number; - next: string | null; - previous: string | null; - results: T[]; -} const HouseModel = types.model('House', { id: types.string, @@ -47,15 +19,11 @@ const AreaModel = types.model('Area', { house: types.maybeNull(HouseModel), }); -const MeterAreaRefModel = types.model('MeterAreaRef', { - id: types.string, -}); - const MeterModel = types .model('Meter', { id: types.identifier, _type: types.array(types.string), - area: MeterAreaRefModel, + area: types.model('MeterAreaRef', { id: types.string }), is_automatic: types.maybeNull(types.boolean), description: types.maybeNull(types.string), installation_date: types.maybeNull(types.string), @@ -122,14 +90,8 @@ const MetersStoreModel = types ); if (!unknownIds.length) return; - const params = new URLSearchParams(); - unknownIds.forEach((id) => params.append('id__in', id)); - - const response: Response = yield fetch(`${BASE_URL}/areas/?${params}`); - if (!response.ok) - throw new Error(`Ошибка загрузки адресов: ${response.status}`); - - const data: PagedResponse = yield response.json(); + const data: PagedResponse = + yield metersApi.fetchAreas(unknownIds); data.results.forEach((area) => self.areas.set(area.id, area as Parameters[1]) ); @@ -139,17 +101,10 @@ const MetersStoreModel = types self.isLoading = true; self.error = null; try { - const offset = (page - 1) * PAGE_SIZE; - const params = new URLSearchParams({ - limit: String(PAGE_SIZE), - offset: String(offset), - }); - - const response: Response = yield fetch(`${BASE_URL}/meters/?${params}`); - if (!response.ok) - throw new Error(`Ошибка загрузки счётчиков: ${response.status}`); - - const data: PagedResponse = yield response.json(); + const data: PagedResponse = yield metersApi.fetchMeters( + page, + PAGE_SIZE + ); self.meters = cast(data.results); self.totalCount = data.count; self.currentPage = page; @@ -165,16 +120,7 @@ const MetersStoreModel = types const deleteMeter = flow(function* (meterId: string) { self.deletingId = meterId; try { - const response: Response = yield fetch( - `${BASE_URL}/meters/${meterId}/`, - { - method: 'DELETE', - } - ); - - if (!response.ok) { - throw new Error(`Ошибка удаления: ${response.status}`); - } + yield metersApi.deleteMeter(meterId); const newTotal = self.totalCount - 1; const targetPage = @@ -198,5 +144,6 @@ const MetersStoreModel = types }); export type MetersStoreType = Instance; +export type MeterType = MetersStoreType['meters'][number]; export const metersStore = MetersStoreModel.create({});