после загрузки основного функционала
This commit is contained in:
@@ -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<FiltersState>({})
|
||||
const [visibleColumns, setVisibleColumns] = useState<VisibilityState>({})
|
||||
|
||||
export async function login(data: TokenObtainPair): Promise<JwtTokenResponse | null>
|
||||
// Инициализация видимости колонок
|
||||
useMemo(() => {
|
||||
if (!schema){
|
||||
return;
|
||||
}
|
||||
|
||||
export async function refreshToken(data: TokenRefresh): Promise<JwtTokenResponse | null>
|
||||
const initialVisibility: VisibilityState = {}
|
||||
schema.forEach(field => {
|
||||
initialVisibility[field.name] = true
|
||||
})
|
||||
setVisibleColumns(initialVisibility)
|
||||
}, [schema])
|
||||
|
||||
export async function verifyToken(data: TokenVerify): Promise<boolean>
|
||||
```
|
||||
// Обновление фильтров
|
||||
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<Account | undefined>(() => {
|
||||
const storedUser = localStorage.getItem('user');
|
||||
return storedUser ? JSON.parse(storedUser) : undefined;
|
||||
});
|
||||
const [tokens, setTokens] = useState<JwtTokenResponse | undefined>(() => {
|
||||
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 (
|
||||
<UserContext.Provider value={{ user, login: handleLogin, logout: handleLogout }}>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
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()
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user