Исправлены критические ошибки типизации и React Hooks
• Исправлена ошибка React Hooks в EmployeesDashboard - перемещен useMemo на верхний уровень компонента • Устранены ошибки TypeScript в ScheduleRecord интерфейсе • Добавлена типизация GraphQL скаляров и резолверов • Исправлены типы Apollo Client и error handling • Очищены неиспользуемые импорты в компонентах Employee • Переименованы неиспользуемые переменные в warehouse-statistics • Исправлен экспорт RefreshCw иконки 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -1,21 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
User,
|
||||
UserPlus,
|
||||
Phone,
|
||||
Mail,
|
||||
Briefcase,
|
||||
DollarSign,
|
||||
AlertCircle,
|
||||
Save,
|
||||
X,
|
||||
Camera,
|
||||
Calendar,
|
||||
MessageCircle,
|
||||
FileImage,
|
||||
RefreshCw,
|
||||
} from 'lucide-react'
|
||||
import { User, UserPlus, AlertCircle, Save, X, Camera, RefreshCw } from 'lucide-react'
|
||||
import { useState, useRef } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
|
@ -6,16 +6,15 @@ import {
|
||||
X,
|
||||
Save,
|
||||
UserPlus,
|
||||
Phone,
|
||||
Mail,
|
||||
Briefcase,
|
||||
DollarSign,
|
||||
FileText,
|
||||
MessageCircle,
|
||||
AlertCircle,
|
||||
Calendar,
|
||||
RefreshCw,
|
||||
FileImage,
|
||||
Briefcase,
|
||||
Phone,
|
||||
Mail,
|
||||
Calendar,
|
||||
DollarSign,
|
||||
MessageCircle,
|
||||
} from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import { useState, useRef } from 'react'
|
||||
@ -23,23 +22,16 @@ import { toast } from 'sonner'
|
||||
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import {
|
||||
formatPhoneInput,
|
||||
formatPassportSeries,
|
||||
formatPassportNumber,
|
||||
formatSalary,
|
||||
formatNameInput,
|
||||
isValidEmail,
|
||||
isValidPhone,
|
||||
isValidPassportSeries,
|
||||
isValidPassportNumber,
|
||||
isValidBirthDate,
|
||||
isValidHireDate,
|
||||
isValidSalary,
|
||||
} from '@/lib/input-masks'
|
||||
|
||||
|
@ -97,6 +97,17 @@ const EmployeesDashboard = React.memo(() => {
|
||||
|
||||
const employees = useMemo(() => data?.myEmployees || [], [data?.myEmployees])
|
||||
|
||||
// Фильтрация сотрудников на верхнем уровне компонента (исправление Rules of Hooks)
|
||||
const filteredEmployees = useMemo(
|
||||
() =>
|
||||
employees.filter(
|
||||
(employee: Employee) =>
|
||||
`${employee.firstName} ${employee.lastName}`.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
employee.position.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
),
|
||||
[employees, searchQuery],
|
||||
)
|
||||
|
||||
// Загружаем данные табеля для всех сотрудников
|
||||
useEffect(() => {
|
||||
const loadScheduleData = async () => {
|
||||
@ -141,7 +152,8 @@ const EmployeesDashboard = React.memo(() => {
|
||||
setShowAddForm(false) // Закрываем форму добавления если открыта
|
||||
}, [])
|
||||
|
||||
const handleEmployeeSaved = useCallback(async (employeeData: Partial<Employee>) => {
|
||||
const handleEmployeeSaved = useCallback(
|
||||
async (employeeData: Partial<Employee>) => {
|
||||
try {
|
||||
if (editingEmployee) {
|
||||
// Обновление существующего сотрудника
|
||||
@ -171,9 +183,12 @@ const EmployeesDashboard = React.memo(() => {
|
||||
console.error('Error saving employee:', error)
|
||||
toast.error('Ошибка при сохранении сотрудника')
|
||||
}
|
||||
}, [editingEmployee, updateEmployee, createEmployee, refetch])
|
||||
},
|
||||
[editingEmployee, updateEmployee, createEmployee, refetch],
|
||||
)
|
||||
|
||||
const handleCreateEmployee = useCallback(async (employeeData: Partial<Employee>) => {
|
||||
const handleCreateEmployee = useCallback(
|
||||
async (employeeData: Partial<Employee>) => {
|
||||
setCreateLoading(true)
|
||||
try {
|
||||
const { data } = await createEmployee({
|
||||
@ -190,9 +205,12 @@ const EmployeesDashboard = React.memo(() => {
|
||||
} finally {
|
||||
setCreateLoading(false)
|
||||
}
|
||||
}, [createEmployee, refetch])
|
||||
},
|
||||
[createEmployee, refetch],
|
||||
)
|
||||
|
||||
const handleEmployeeDeleted = useCallback(async (employeeId: string) => {
|
||||
const handleEmployeeDeleted = useCallback(
|
||||
async (employeeId: string) => {
|
||||
try {
|
||||
setDeletingEmployeeId(employeeId)
|
||||
const { data } = await deleteEmployee({
|
||||
@ -208,10 +226,13 @@ const EmployeesDashboard = React.memo(() => {
|
||||
} finally {
|
||||
setDeletingEmployeeId(null)
|
||||
}
|
||||
}, [deleteEmployee, refetch])
|
||||
},
|
||||
[deleteEmployee, refetch],
|
||||
)
|
||||
|
||||
// Функция для изменения статуса дня в табеле
|
||||
const changeDayStatus = useCallback(async (employeeId: string, day: number, currentStatus: string) => {
|
||||
const changeDayStatus = useCallback(
|
||||
async (employeeId: string, day: number, currentStatus: string) => {
|
||||
try {
|
||||
// Циклично переключаем статусы
|
||||
const statuses = ['WORK', 'WEEKEND', 'VACATION', 'SICK', 'ABSENT']
|
||||
@ -242,12 +263,16 @@ const EmployeesDashboard = React.memo(() => {
|
||||
const currentSchedule = prev[employeeId] || []
|
||||
const existingRecordIndex = currentSchedule.findIndex((record) => record.date.split('T')[0] === dateStr)
|
||||
|
||||
const newRecord = {
|
||||
const newRecord: ScheduleRecord = {
|
||||
id: Date.now().toString(), // временный ID
|
||||
date: updatedDate.toISOString(),
|
||||
status: nextStatus,
|
||||
hoursWorked: hours,
|
||||
employee: { id: employeeId },
|
||||
employee: {
|
||||
id: employeeId,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
},
|
||||
}
|
||||
|
||||
let updatedSchedule
|
||||
@ -274,7 +299,9 @@ const EmployeesDashboard = React.memo(() => {
|
||||
console.error('Error updating day status:', error)
|
||||
toast.error('Ошибка при обновлении статуса дня')
|
||||
}
|
||||
}, [updateEmployeeSchedule, currentYear, currentMonth, setEmployeeSchedules])
|
||||
},
|
||||
[updateEmployeeSchedule, currentYear, currentMonth, setEmployeeSchedules],
|
||||
)
|
||||
|
||||
// Функция для обновления данных дня из модалки
|
||||
const updateDayData = async (
|
||||
@ -309,14 +336,18 @@ const EmployeesDashboard = React.memo(() => {
|
||||
const currentSchedule = prev[employeeId] || []
|
||||
const existingRecordIndex = currentSchedule.findIndex((record) => record.date.split('T')[0] === dateStr)
|
||||
|
||||
const newRecord = {
|
||||
const newRecord: ScheduleRecord = {
|
||||
id: Date.now().toString(), // временный ID
|
||||
date: date.toISOString(),
|
||||
status: data.status,
|
||||
hoursWorked: data.hoursWorked,
|
||||
overtimeHours: data.overtimeHours,
|
||||
notes: data.notes,
|
||||
employee: { id: employeeId },
|
||||
employee: {
|
||||
id: employeeId,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
},
|
||||
}
|
||||
|
||||
let updatedSchedule
|
||||
@ -529,18 +560,9 @@ ${employees.map((emp: Employee) => `• ${emp.firstName} ${emp.lastName} - ${emp
|
||||
{/* Контент табов */}
|
||||
<TabsContent value="combined">
|
||||
<Card className="glass-card p-6">
|
||||
{(() => {
|
||||
const filteredEmployees = useMemo(() => employees.filter(
|
||||
(employee: Employee) =>
|
||||
`${employee.firstName} ${employee.lastName}`.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
employee.position.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
), [employees, searchQuery])
|
||||
|
||||
if (filteredEmployees.length === 0) {
|
||||
return <EmployeeEmptyState searchQuery={searchQuery} onShowAddForm={() => setShowAddForm(true)} />
|
||||
}
|
||||
|
||||
return (
|
||||
{filteredEmployees.length === 0 ? (
|
||||
<EmployeeEmptyState searchQuery={searchQuery} onShowAddForm={() => setShowAddForm(true)} />
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Навигация по месяцам и легенда в одной строке */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
@ -618,8 +640,7 @@ ${employees.map((emp: Employee) => `• ${emp.firstName} ${emp.lastName} - ${emp
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
)}
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
|
@ -11,7 +11,7 @@ export {
|
||||
Download,
|
||||
Search,
|
||||
Filter,
|
||||
Refresh as RefreshCw,
|
||||
RefreshCw,
|
||||
|
||||
// Навигация
|
||||
ArrowLeft,
|
||||
|
@ -36,18 +36,18 @@ export function WarehouseStatistics({ products }: WarehouseStatisticsProps) {
|
||||
const totalStock = products.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0)
|
||||
const totalOrdered = products.reduce((sum, p) => sum + (p.ordered || 0), 0)
|
||||
const totalInTransit = products.reduce((sum, p) => sum + (p.inTransit || 0), 0)
|
||||
const totalSold = products.reduce((sum, p) => sum + (p.sold || 0), 0)
|
||||
const _totalSold = products.reduce((sum, p) => sum + (p.sold || 0), 0)
|
||||
|
||||
// Статистика по товарам
|
||||
const goodsStock = goods.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0)
|
||||
const goodsOrdered = goods.reduce((sum, p) => sum + (p.ordered || 0), 0)
|
||||
const goodsInTransit = goods.reduce((sum, p) => sum + (p.inTransit || 0), 0)
|
||||
const _goodsOrdered = goods.reduce((sum, p) => sum + (p.ordered || 0), 0)
|
||||
const _goodsInTransit = goods.reduce((sum, p) => sum + (p.inTransit || 0), 0)
|
||||
const goodsSold = goods.reduce((sum, p) => sum + (p.sold || 0), 0)
|
||||
|
||||
// Статистика по расходникам
|
||||
const consumablesStock = consumables.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0)
|
||||
const consumablesOrdered = consumables.reduce((sum, p) => sum + (p.ordered || 0), 0)
|
||||
const consumablesInTransit = consumables.reduce((sum, p) => sum + (p.inTransit || 0), 0)
|
||||
const _consumablesOrdered = consumables.reduce((sum, p) => sum + (p.ordered || 0), 0)
|
||||
const _consumablesInTransit = consumables.reduce((sum, p) => sum + (p.inTransit || 0), 0)
|
||||
const consumablesSold = consumables.reduce((sum, p) => sum + (p.sold || 0), 0)
|
||||
|
||||
// Товары с низкими остатками
|
||||
|
@ -21,10 +21,10 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
|
||||
}
|
||||
|
||||
for (const resolver of resolvers) {
|
||||
if (resolver.Query) {
|
||||
if (resolver?.Query) {
|
||||
Object.assign(result.Query, resolver.Query)
|
||||
}
|
||||
if (resolver.Mutation) {
|
||||
if (resolver?.Mutation) {
|
||||
Object.assign(result.Mutation, resolver.Mutation)
|
||||
}
|
||||
// Объединяем другие типы резолверов (например, Employee, Organization и т.д.)
|
||||
@ -33,10 +33,12 @@ const mergeResolvers = (...resolvers: ResolverObject[]): ResolverObject => {
|
||||
if (!result[key]) {
|
||||
result[key] = {}
|
||||
}
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
Object.assign(result[key], value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { GraphQLScalarType, Kind } from 'graphql'
|
||||
import { GraphQLScalarType, Kind, ValueNode } from 'graphql'
|
||||
|
||||
export const JSONScalar = new GraphQLScalarType({
|
||||
export const JSONScalar: GraphQLScalarType = new GraphQLScalarType({
|
||||
name: 'JSON',
|
||||
serialize: (value) => value,
|
||||
parseValue: (value) => value,
|
||||
parseLiteral: (ast) => {
|
||||
serialize: (value: any): any => value,
|
||||
parseValue: (value: any): any => value,
|
||||
parseLiteral: (ast: ValueNode): any => {
|
||||
switch (ast.kind) {
|
||||
case Kind.STRING:
|
||||
case Kind.BOOLEAN:
|
||||
|
@ -49,7 +49,7 @@ const errorLink = onError(({ graphQLErrors, networkError, operation, forward: _f
|
||||
graphQLErrorsLength: graphQLErrors?.length || 0,
|
||||
hasNetworkError: !!networkError,
|
||||
operationName: operation?.operationName || 'Unknown',
|
||||
operationType: operation?.query?.definitions?.[0]?.operation || 'Unknown',
|
||||
operationType: (operation?.query?.definitions?.[0] as any)?.operation || 'Unknown',
|
||||
variables: operation?.variables || {},
|
||||
}
|
||||
|
||||
@ -85,7 +85,7 @@ const errorLink = onError(({ graphQLErrors, networkError, operation, forward: _f
|
||||
try {
|
||||
console.warn('🌐 Network Error:', {
|
||||
message: networkError.message || 'No message',
|
||||
statusCode: networkError.statusCode || 'No status',
|
||||
statusCode: (networkError as any).statusCode || 'No status',
|
||||
operation: operation?.operationName || 'Unknown',
|
||||
})
|
||||
} catch (innerError) {
|
||||
|
@ -89,7 +89,7 @@ export async function ensureCategories() {
|
||||
})
|
||||
createdCount++
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
// Игнорируем ошибки дублирования
|
||||
if (error.code !== 'P2002') {
|
||||
console.error(`Ошибка создания категории "${categoryName}":`, error.message)
|
||||
|
Reference in New Issue
Block a user