diff --git a/StaticHelper/__init__.py b/StaticHelper/__init__.py index 8603169..3cd288e 100644 --- a/StaticHelper/__init__.py +++ b/StaticHelper/__init__.py @@ -2,5 +2,5 @@ from StaticHelper.ai_interface import AIInterface if __name__ == '__main__': ai = AIInterface() - ai.work("") + ai.work("")#Создай экран. Используй данные из документации. Создавай className для каждого элемента") diff --git a/StaticHelper/source/docs.md b/StaticHelper/source/docs.md index 976ab7c..9278b7d 100644 --- a/StaticHelper/source/docs.md +++ b/StaticHelper/source/docs.md @@ -50,6 +50,12 @@ export interface PageControl{ nextPage(): void; previousPage(): void; } +export interface FieldInfo { + name: string; + type: string; + title: string; + format?: string; +} ``` `types/survey.ts` ```ts @@ -113,12 +119,10 @@ export interface ContactData{ phone?: string; email?:string; } -interface Role extends UniqueItem{ - title: string; -} export interface Account extends UniqueItem, ContactData{ login: string; - role?: Role; + role?: number; + roleTitle?: string; is_staff: boolean; is_active: boolean; } @@ -235,6 +239,21 @@ export function ProfessionPage() { } ``` +### Автодокументация +`api/hooks.ts` +```ts +/** + * Хук для получения документации swagger JSON и извлечения структур. + * @param applicationName Название модуля/приложения. + * @param endpointName Название эндпоинта. + * @returns Массив структур без ID с полями: название, тип, title, format. + */ +export function useApiDocumentation( + applicationName: string, + endpointName: string +): { schema: FieldInfo[] | null; loading: boolean; error: Error | null } +``` + ### Авторизация diff --git a/StaticHelper/source/old_input.md b/StaticHelper/source/old_input.md index e69de29..4556427 100644 --- a/StaticHelper/source/old_input.md +++ b/StaticHelper/source/old_input.md @@ -0,0 +1,189 @@ + + +```ts + +import { useState, useMemo, useCallback } from 'react' +import { FieldInfo } from '../types/common' +import {FiltersState, VisibilityState} from "../types/filters.ts"; + + + +/** + * Хук для управления фильтрами и видимостью колонок таблицы. + * @param schema Схема полей таблицы. + * @param initialData + * @returns Объекты состояния фильтров и видимости, а также функции для их изменения. + */ +export function useTableFilters(schema: FieldInfo[]| null, initialData: unknown[] | null) { + const [filters, setFilters] = useState({}) + const [visibleColumns, setVisibleColumns] = useState({}) + + // Инициализация видимости колонок + useMemo(() => { + if (!schema){ + return; + } + + const initialVisibility: VisibilityState = {} + schema.forEach(field => { + initialVisibility[field.name] = true + }) + setVisibleColumns(initialVisibility) + }, [schema]) + + // Обновление фильтров + const handleFilterChange = useCallback((name: string, filterPart: unknown) => { + setFilters(prev => ({ + ...prev, + [name]: { + ...prev[name], + ...filterPart + } + })) + }, []) + + // Переключатель видимости колонки + const handleColumnToggle = useCallback((name: string) => { + setVisibleColumns(prev => ({ + ...prev, + [name]: !prev[name] + })) + }, []) + + // Логика фильтрации данных, вынесенная из компонента + const filteredData = useMemo(() => { + if (!initialData || !schema){ + return ; + } + return initialData.filter(item => { + return schema.every(field => { + const value = item[field.name] + const filter = filters[field.name] + if (!filter) return true + + // Обработка по типу + if (field.type === 'string') { + if (filter.searchText) { + if (typeof value !== 'string') return false + if (!value.toLowerCase().includes(filter.searchText.toLowerCase())) return false + } + if (filter.exactMatch && filter.searchText) { + return value.toLowerCase() === filter.searchText.toLowerCase() + } + if (filter.empty !== undefined) { + const isEmpty = value === '' || value === null || value === undefined + if (filter.empty && !isEmpty) return false + if (filter.notEmpty && isEmpty) return false + } + } else if (field.format === 'date') { + const dateValue = new Date(value as string).getTime() + if (filter.fromDate) { + const fromTime = new Date(filter.fromDate).getTime() + if (dateValue < fromTime) return false + } + if (filter.toDate) { + const toTime = new Date(filter.toDate).getTime() + if (dateValue > toTime) return false + } + } else if (field.type === 'number') { + const numValue = Number(value) + if (filter.gte !== undefined && numValue < filter.gte) return false + if (filter.lte !== undefined && numValue > filter.lte) return false + } + return true + }) + }) + }, [initialData, filters, schema]) + + return { + filters, + setFilters, + visibleColumns, + setVisibleColumns, + handleFilterChange, + handleColumnToggle, + filteredData + } +} + + +export function FilteredTable({ schema, filteredData, visibleColumns}: FilteredTableProps) { + + return ( +
+ + + + + + {schema.map( + (field) => + visibleColumns[field.name] && ( + + ) + )} + + + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + {schema.map( + (field) => + visibleColumns[field.name] && ( + + ) + )} + + )) + ) : ( + + + + )} + +
+ {field.title} +
+ {item[field.name] ?? "-"} +
+ Нет данных для отображения +
+
+ ) +} + +``` + + + + + + +```ts +const { + filters, + handleFilterChange, + visibleColumns, + handleColumnToggle, + filteredData + } = useTableFilters(schema, dataPage.items) + + + const handleExport = async () => { + if (!filteredData || filteredData.length === 0) { + alert('Нет данных для экспорта') + return + } + + + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }) + saveAs(blob, "Отчёт.") + }; + +``` + + +в handleExport сохранять csv \ No newline at end of file diff --git a/StaticHelper/source/temp.md b/StaticHelper/source/temp.md new file mode 100644 index 0000000..aec9114 --- /dev/null +++ b/StaticHelper/source/temp.md @@ -0,0 +1,9 @@ +`API/users.ts` +```ts + +export async function login(data: TokenObtainPair): Promise + +export async function refreshToken(data: TokenRefresh): Promise + +export async function verifyToken(data: TokenVerify): Promise +``` \ No newline at end of file diff --git a/StaticHelper/source/working_file.md b/StaticHelper/source/working_file.md index c3c9c3d..c92d0ca 100644 --- a/StaticHelper/source/working_file.md +++ b/StaticHelper/source/working_file.md @@ -1,95 +1,138 @@ -`API/users.ts` +`components/useTableFilters.tsx` ```ts +// добавьте новую функцию для экспорта CSV +export function useTableFilters(schema: FieldInfo[]| null, initialData: unknown[] | null) { + const [filters, setFilters] = useState({}) + const [visibleColumns, setVisibleColumns] = useState({}) -export async function login(data: TokenObtainPair): Promise + // Инициализация видимости колонок + useMemo(() => { + if (!schema){ + return; + } -export async function refreshToken(data: TokenRefresh): Promise + const initialVisibility: VisibilityState = {} + schema.forEach(field => { + initialVisibility[field.name] = true + }) + setVisibleColumns(initialVisibility) + }, [schema]) -export async function verifyToken(data: TokenVerify): Promise -``` + // Обновление фильтров + const handleFilterChange = useCallback((name: string, filterPart: unknown) => { + setFilters(prev => ({ + ...prev, + [name]: { + ...prev[name], + ...filterPart + } + })) + }, []) -`utils/users/UserProvider.tsx` -```tsx -export function UserProvider({ children }: { children: ReactNode }) { - const [user, setUser] = useState(() => { - const storedUser = localStorage.getItem('user'); - return storedUser ? JSON.parse(storedUser) : undefined; - }); - const [tokens, setTokens] = useState(() => { - const storedTokens = localStorage.getItem('tokens'); - return storedTokens ? JSON.parse(storedTokens) : undefined; - }); + // Переключатель видимости колонки + const handleColumnToggle = useCallback((name: string) => { + setVisibleColumns(prev => ({ + ...prev, + [name]: !prev[name] + })) + }, []) - useEffect(() => { - if (tokens?.access) { - localStorage.setItem('tokens', JSON.stringify(tokens)); - } else { - localStorage.removeItem('tokens'); + // Функция экспорта данных в CSV + const handleExportCSV = useCallback(() => { + if (!initialData || !schema || initialData.length === 0) { + alert('Нет данных для экспорта') + return + } + + const visibleFields = schema.filter(field => visibleColumns[field.name]) + const header = visibleFields.map(field => `"${field.title.replace(/"/g, '""')}"`).join(';') + + const rows = initialData + .filter(item => schema.every(field => { + const value = item[field.name] + const filter = filters[field.name] + if (!filter) return true + + if (field.type === 'string') { + if (filter.searchText) { + if (typeof value !== 'string') return false + if (!value.toLowerCase().includes(filter.searchText.toLowerCase())) return false + } + if (filter.exactMatch && filter.searchText) { + return value.toLowerCase() === filter.searchText.toLowerCase() + } + if (filter.empty !== undefined) { + const isEmpty = value === '' || value === null || value === undefined + if (filter.empty && !isEmpty) return false + if (filter.notEmpty && isEmpty) return false + } + } else if (field.format === 'date') { + const dateValue = new Date(value as string).getTime() + if (filter.fromDate) { + const fromTime = new Date(filter.fromDate).getTime() + if (dateValue < fromTime) return false + } + if (filter.toDate) { + const toTime = new Date(filter.toDate).getTime() + if (dateValue > toTime) return false + } + } else if (field.type === 'number') { + const numValue = Number(value) + if (filter.gte !== undefined && numValue < filter.gte) return false + if (filter.lte !== undefined && numValue > filter.lte) return false } - }, [tokens]); + return true + })) + .map(item => { + const row = visibleFields.map(field => { + const val = item[field.name] + // Обработка null/undefined + const strVal = val !== undefined && val !== null ? String(val) : '' + // Экранирование кавычек + return `"${strVal.replace(/"/g, '""')}"` - useEffect(() => { - if (user) { - localStorage.setItem('user', JSON.stringify(user)); - } else { - localStorage.removeItem('user'); - } - }, [user]); + }) + return row.join(';') + }) - const handleLogin = async (credentials: TokenObtainPair) => { - const tokens = await apiLogin(credentials); - if (tokens) { - setTokens(tokens); - // Здесь можно добавить запрос для получения данных пользователя - setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO - } - }; + const csvContent = [header, ...rows].join('\n') + // Создаем ссылку для скачивания + const blob = new Blob([csvContent], { type: 'text/csv' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = 'export.csv' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + URL.revokeObjectURL(url) + }, [initialData, schema, filters, visibleColumns]) - const handleLogout = () => { - setUser(undefined); - setTokens(undefined); - }; - - const validateToken = async () => { - if (tokens?.access) { - const isValid = await verifyToken({ token: tokens.access }); - if (!isValid && tokens.refresh) { - const newTokens = await refreshToken({ refresh: tokens.refresh }); - if (newTokens) { - setTokens(newTokens); - } else { - handleLogout(); - } - } else if (!isValid) { - handleLogout(); - } - } - }; - - useEffect(() => { - validateToken().then(); - }, []); - - return ( - - {children} - - ); + return { + filters, + setFilters, + visibleColumns, + setVisibleColumns, + handleFilterChange, + handleColumnToggle, + filteredData, + handleExportCSV // добавьте сюда + } } ``` - -В частности обрати внимание на эту функцию +`Ваш файл, где вызывается handleExport` (примеры использования) ```ts -const handleLogin = async (credentials: TokenObtainPair) => { - const tokens = await apiLogin(credentials); - if (tokens) { - setTokens(tokens); - // Здесь можно добавить запрос для получения данных пользователя - setUser({ id: 1, login: credentials.login, is_staff: false, is_active: true } as Account); // TODO - } -}; -``` - -Воспользуйся эндпоинтами /users/auth/users/me/ для получения id текущего пользователя +const { + filters, + handleFilterChange, + visibleColumns, + handleColumnToggle, + filteredData, + handleExportCSV // новая функция +} = useTableFilters(schema, dataPage.items) +const handleExport = async () => { + handleExportCSV() +} +``` \ No newline at end of file