Compare commits
10 Commits
914ae0528f
...
76b72dce1c
| Author | SHA1 | Date | |
|---|---|---|---|
| 76b72dce1c | |||
| 0a7fd16a29 | |||
| b8bc7023a0 | |||
| f0a712eb9d | |||
| c609c6471d | |||
| 3a22fd05be | |||
| 3f3762c066 | |||
| 118ae84930 | |||
| 7a2733cda6 | |||
| 7a2ef98fd5 |
@ -1,8 +1,8 @@
|
|||||||
from typing import Sequence, Optional
|
from typing import Sequence, Optional
|
||||||
|
from sqlalchemy import select, desc, func
|
||||||
from sqlalchemy import select, desc
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.domain.models import Appointment
|
from app.domain.models import Appointment
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ class AppointmentsRepository:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self) -> Sequence[Appointment]:
|
async def get_all(self, start_date: date | None = None, end_date: date | None = None) -> Sequence[Appointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(Appointment)
|
select(Appointment)
|
||||||
.options(joinedload(Appointment.type))
|
.options(joinedload(Appointment.type))
|
||||||
@ -19,10 +19,15 @@ class AppointmentsRepository:
|
|||||||
.options(joinedload(Appointment.doctor))
|
.options(joinedload(Appointment.doctor))
|
||||||
.order_by(desc(Appointment.appointment_datetime))
|
.order_by(desc(Appointment.appointment_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_doctor_id(self, doctor_id: int) -> Sequence[Appointment]:
|
async def get_by_doctor_id(self, doctor_id: int, start_date: date | None = None, end_date: date | None = None) -> \
|
||||||
|
Sequence[Appointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(Appointment)
|
select(Appointment)
|
||||||
.options(joinedload(Appointment.type))
|
.options(joinedload(Appointment.type))
|
||||||
@ -31,10 +36,29 @@ class AppointmentsRepository:
|
|||||||
.filter_by(doctor_id=doctor_id)
|
.filter_by(doctor_id=doctor_id)
|
||||||
.order_by(desc(Appointment.appointment_datetime))
|
.order_by(desc(Appointment.appointment_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_patient_id(self, patient_id: int) -> Sequence[Appointment]:
|
async def get_upcoming_by_doctor_id(self, doctor_id: int) -> Sequence[Appointment]:
|
||||||
|
stmt = (
|
||||||
|
select(Appointment)
|
||||||
|
.options(joinedload(Appointment.type))
|
||||||
|
.options(joinedload(Appointment.patient))
|
||||||
|
.options(joinedload(Appointment.doctor))
|
||||||
|
.filter_by(doctor_id=doctor_id)
|
||||||
|
.filter(Appointment.appointment_datetime >= func.now())
|
||||||
|
.order_by(Appointment.appointment_datetime)
|
||||||
|
.limit(5)
|
||||||
|
)
|
||||||
|
result = await self.db.execute(stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def get_by_patient_id(self, patient_id: int, start_date: date | None = None, end_date: date | None = None) -> \
|
||||||
|
Sequence[Appointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(Appointment)
|
select(Appointment)
|
||||||
.options(joinedload(Appointment.type))
|
.options(joinedload(Appointment.type))
|
||||||
@ -43,6 +67,10 @@ class AppointmentsRepository:
|
|||||||
.filter_by(patient_id=patient_id)
|
.filter_by(patient_id=patient_id)
|
||||||
.order_by(desc(Appointment.appointment_datetime))
|
.order_by(desc(Appointment.appointment_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(Appointment.appointment_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,80 @@
|
|||||||
from typing import Optional, Sequence
|
from datetime import date
|
||||||
|
from typing import Optional, Sequence, Tuple, Literal
|
||||||
|
|
||||||
from sqlalchemy import select, desc
|
from sqlalchemy import select, desc, or_, func, asc
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
|
||||||
from app.domain.models import LensIssue
|
from app.domain.models import LensIssue, Patient, User
|
||||||
|
|
||||||
|
|
||||||
class LensIssuesRepository:
|
class LensIssuesRepository:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self) -> Sequence[LensIssue]:
|
async def get_all(
|
||||||
|
self,
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 10,
|
||||||
|
search: Optional[str] = None,
|
||||||
|
sort_order: Literal["asc", "desc"] = "desc",
|
||||||
|
start_date: Optional[date] = None,
|
||||||
|
end_date: Optional[date] = None
|
||||||
|
) -> Tuple[Sequence[LensIssue], int]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(LensIssue)
|
select(LensIssue)
|
||||||
.options(joinedload(LensIssue.lens))
|
.options(joinedload(LensIssue.lens))
|
||||||
.options(joinedload(LensIssue.patient))
|
.options(joinedload(LensIssue.patient))
|
||||||
.options(joinedload(LensIssue.doctor))
|
.options(joinedload(LensIssue.doctor))
|
||||||
.order_by(desc(LensIssue.issue_date))
|
.join(Patient)
|
||||||
|
.join(User)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if search:
|
||||||
|
search = f"%{search}%"
|
||||||
|
stmt = stmt.filter(
|
||||||
|
or_(
|
||||||
|
Patient.last_name.ilike(search),
|
||||||
|
Patient.first_name.ilike(search),
|
||||||
|
User.last_name.ilike(search),
|
||||||
|
User.first_name.ilike(search)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(LensIssue.issue_date >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(LensIssue.issue_date <= end_date)
|
||||||
|
|
||||||
|
stmt = stmt.order_by(
|
||||||
|
desc(LensIssue.issue_date) if sort_order == "desc" else asc(LensIssue.issue_date)
|
||||||
|
)
|
||||||
|
|
||||||
|
count_stmt = select(func.count()).select_from(LensIssue).join(Patient).join(User)
|
||||||
|
if search:
|
||||||
|
search = f"%{search}%"
|
||||||
|
count_stmt = count_stmt.filter(
|
||||||
|
or_(
|
||||||
|
Patient.last_name.ilike(search),
|
||||||
|
Patient.first_name.ilike(search),
|
||||||
|
User.last_name.ilike(search),
|
||||||
|
User.first_name.ilike(search)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if start_date:
|
||||||
|
count_stmt = count_stmt.filter(LensIssue.issue_date >= start_date)
|
||||||
|
|
||||||
|
if end_date:
|
||||||
|
count_stmt = count_stmt.filter(LensIssue.issue_date <= end_date)
|
||||||
|
|
||||||
|
stmt = stmt.offset(skip).limit(limit)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
issues = result.scalars().all()
|
||||||
|
|
||||||
|
count_result = await self.db.execute(count_stmt)
|
||||||
|
total_count = count_result.scalar()
|
||||||
|
|
||||||
|
return issues, total_count
|
||||||
|
|
||||||
async def get_by_id(self, lens_issue_id: int) -> Optional[LensIssue]:
|
async def get_by_id(self, lens_issue_id: int) -> Optional[LensIssue]:
|
||||||
stmt = select(LensIssue).filter_by(id=lens_issue_id)
|
stmt = select(LensIssue).filter_by(id=lens_issue_id)
|
||||||
|
|||||||
@ -10,8 +10,8 @@ class PatientsRepository:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self, skip: int = 0, limit: int = 10, search: str = None,
|
async def get_all(self, skip: int = 0, limit: int = 100, search: str = None,
|
||||||
sort_order: Literal["asc", "desc"] = "asc") -> Tuple[Sequence[Patient], int]:
|
sort_order: Literal["asc", "desc"] = "asc", all_params: bool = False) -> Tuple[Sequence[Patient], int]:
|
||||||
stmt = select(Patient)
|
stmt = select(Patient)
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
@ -50,6 +50,12 @@ class PatientsRepository:
|
|||||||
count_result = await self.db.execute(count_stmt)
|
count_result = await self.db.execute(count_stmt)
|
||||||
total_count = count_result.scalar()
|
total_count = count_result.scalar()
|
||||||
|
|
||||||
|
if not all_params:
|
||||||
|
stmt = stmt.offset(skip).limit(limit)
|
||||||
|
|
||||||
|
result = await self.db.execute(stmt)
|
||||||
|
patients = result.scalars().all()
|
||||||
|
|
||||||
return patients, total_count
|
return patients, total_count
|
||||||
|
|
||||||
async def get_by_id(self, patient_id: int) -> Optional[Patient]:
|
async def get_by_id(self, patient_id: int) -> Optional[Patient]:
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
from sqlalchemy import select, desc, func
|
||||||
from sqlalchemy import select, desc
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import joinedload
|
from sqlalchemy.orm import joinedload
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.domain.models import ScheduledAppointment
|
from app.domain.models import ScheduledAppointment
|
||||||
|
|
||||||
@ -11,7 +11,8 @@ class ScheduledAppointmentsRepository:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
async def get_all(self) -> Sequence[ScheduledAppointment]:
|
async def get_all(self, start_date: date | None = None, end_date: date | None = None) -> Sequence[
|
||||||
|
ScheduledAppointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(ScheduledAppointment)
|
select(ScheduledAppointment)
|
||||||
.options(joinedload(ScheduledAppointment.type))
|
.options(joinedload(ScheduledAppointment.type))
|
||||||
@ -20,10 +21,15 @@ class ScheduledAppointmentsRepository:
|
|||||||
.filter_by(is_canceled=False)
|
.filter_by(is_canceled=False)
|
||||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_doctor_id(self, doctor_id: int) -> Sequence[ScheduledAppointment]:
|
async def get_by_doctor_id(self, doctor_id: int, start_date: date | None = None, end_date: date | None = None) -> \
|
||||||
|
Sequence[ScheduledAppointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(ScheduledAppointment)
|
select(ScheduledAppointment)
|
||||||
.options(joinedload(ScheduledAppointment.type))
|
.options(joinedload(ScheduledAppointment.type))
|
||||||
@ -32,10 +38,29 @@ class ScheduledAppointmentsRepository:
|
|||||||
.filter_by(doctor_id=doctor_id, is_canceled=False)
|
.filter_by(doctor_id=doctor_id, is_canceled=False)
|
||||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def get_by_patient_id(self, patient_id: int) -> Sequence[ScheduledAppointment]:
|
async def get_upcoming_by_doctor_id(self, doctor_id: int) -> Sequence[ScheduledAppointment]:
|
||||||
|
stmt = (
|
||||||
|
select(ScheduledAppointment)
|
||||||
|
.options(joinedload(ScheduledAppointment.type))
|
||||||
|
.options(joinedload(ScheduledAppointment.patient))
|
||||||
|
.options(joinedload(ScheduledAppointment.doctor))
|
||||||
|
.filter_by(doctor_id=doctor_id, is_canceled=False)
|
||||||
|
.filter(ScheduledAppointment.scheduled_datetime >= func.now())
|
||||||
|
.order_by(ScheduledAppointment.scheduled_datetime)
|
||||||
|
.limit(5)
|
||||||
|
)
|
||||||
|
result = await self.db.execute(stmt)
|
||||||
|
return result.scalars().all()
|
||||||
|
|
||||||
|
async def get_by_patient_id(self, patient_id: int, start_date: date | None = None, end_date: date | None = None) -> \
|
||||||
|
Sequence[ScheduledAppointment]:
|
||||||
stmt = (
|
stmt = (
|
||||||
select(ScheduledAppointment)
|
select(ScheduledAppointment)
|
||||||
.options(joinedload(ScheduledAppointment.type))
|
.options(joinedload(ScheduledAppointment.type))
|
||||||
@ -44,6 +69,10 @@ class ScheduledAppointmentsRepository:
|
|||||||
.filter_by(patient_id=patient_id, is_canceled=False)
|
.filter_by(patient_id=patient_id, is_canceled=False)
|
||||||
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
.order_by(desc(ScheduledAppointment.scheduled_datetime))
|
||||||
)
|
)
|
||||||
|
if start_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime >= start_date)
|
||||||
|
if end_date:
|
||||||
|
stmt = stmt.filter(ScheduledAppointment.scheduled_datetime <= end_date)
|
||||||
result = await self.db.execute(stmt)
|
result = await self.db.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.database.session import get_db
|
from app.database.session import get_db
|
||||||
from app.domain.entities.appointment import AppointmentEntity
|
from app.domain.entities.appointment import AppointmentEntity
|
||||||
@ -18,9 +19,11 @@ router = APIRouter()
|
|||||||
async def get_all_appointments(
|
async def get_all_appointments(
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
appointments_service = AppointmentsService(db)
|
appointments_service = AppointmentsService(db)
|
||||||
return await appointments_service.get_all_appointments()
|
return await appointments_service.get_all_appointments(start_date=start_date, end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@ -33,9 +36,26 @@ async def get_all_appointments_by_doctor_id(
|
|||||||
doctor_id: int,
|
doctor_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
appointments_service = AppointmentsService(db)
|
appointments_service = AppointmentsService(db)
|
||||||
return await appointments_service.get_appointments_by_doctor_id(doctor_id)
|
return await appointments_service.get_appointments_by_doctor_id(doctor_id, start_date=start_date, end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/doctor/{doctor_id}/upcoming/",
|
||||||
|
response_model=list[AppointmentEntity],
|
||||||
|
summary="Get upcoming appointments for doctor",
|
||||||
|
description="Returns the next 5 upcoming appointments for doctor",
|
||||||
|
)
|
||||||
|
async def get_upcoming_appointments_by_doctor_id(
|
||||||
|
doctor_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
appointments_service = AppointmentsService(db)
|
||||||
|
return await appointments_service.get_upcoming_appointments_by_doctor_id(doctor_id)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@ -48,9 +68,12 @@ async def get_all_appointments_by_patient_id(
|
|||||||
patient_id: int,
|
patient_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
appointments_service = AppointmentsService(db)
|
appointments_service = AppointmentsService(db)
|
||||||
return await appointments_service.get_appointments_by_patient_id(patient_id)
|
return await appointments_service.get_appointments_by_patient_id(patient_id, start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
from fastapi import APIRouter, Depends
|
from datetime import date
|
||||||
|
from typing import Optional, Literal
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, Query
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.database.session import get_db
|
from app.database.session import get_db
|
||||||
from app.domain.entities.lens_issues import LensIssueEntity
|
from app.domain.entities.lens_issues import LensIssueEntity
|
||||||
|
from app.domain.entities.responses.paginated_issue import PaginatedLensIssuesResponseEntity
|
||||||
from app.infrastructure.dependencies import get_current_user
|
from app.infrastructure.dependencies import get_current_user
|
||||||
from app.infrastructure.lens_issues_service import LensIssuesService
|
from app.infrastructure.lens_issues_service import LensIssuesService
|
||||||
|
|
||||||
@ -11,17 +15,34 @@ router = APIRouter()
|
|||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/",
|
"/",
|
||||||
response_model=list[LensIssueEntity],
|
response_model=PaginatedLensIssuesResponseEntity,
|
||||||
summary="Get all lens issues",
|
summary="Get all lens issues",
|
||||||
description="Returns a list of all lens issues",
|
description="Returns a paginated list of lens issues with optional filtering and sorting",
|
||||||
)
|
)
|
||||||
async def get_all_lens_issues(
|
async def get_all_lens_issues(
|
||||||
|
page: int = Query(1, ge=1),
|
||||||
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
|
search: Optional[str] = Query(None),
|
||||||
|
sort_order: Literal["asc", "desc"] = Query("desc"),
|
||||||
|
start_date: Optional[date] = Query(None),
|
||||||
|
end_date: Optional[date] = Query(None),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
lens_issues_service = LensIssuesService(db)
|
lens_issues_service = LensIssuesService(db)
|
||||||
return await lens_issues_service.get_all_lens_issues()
|
skip = (page - 1) * page_size
|
||||||
|
issues, total_count = await lens_issues_service.get_all_lens_issues(
|
||||||
|
skip=skip,
|
||||||
|
limit=page_size,
|
||||||
|
search=search,
|
||||||
|
sort_order=sort_order,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
return PaginatedLensIssuesResponseEntity(
|
||||||
|
issues=issues,
|
||||||
|
total_count=total_count
|
||||||
|
)
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/",
|
"/",
|
||||||
|
|||||||
@ -23,11 +23,12 @@ async def get_all_patients(
|
|||||||
page_size: int = Query(10, ge=1, le=100, description="Number of patients per page"),
|
page_size: int = Query(10, ge=1, le=100, description="Number of patients per page"),
|
||||||
search: str = Query(None, description="Search term for filtering patients"),
|
search: str = Query(None, description="Search term for filtering patients"),
|
||||||
sort_order: Literal["asc", "desc"] = Query("asc", description="Sort order by first_name (asc or desc)"),
|
sort_order: Literal["asc", "desc"] = Query("asc", description="Sort order by first_name (asc or desc)"),
|
||||||
|
all_params: bool = Query(False, description="Get all patients"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
):
|
):
|
||||||
patients_service = PatientsService(db)
|
patients_service = PatientsService(db)
|
||||||
patients, total_count = await patients_service.get_all_patients(page, page_size, search, sort_order)
|
patients, total_count = await patients_service.get_all_patients(page, page_size, search, sort_order, all_params)
|
||||||
return {"patients": patients, "total_count": total_count}
|
return {"patients": patients, "total_count": total_count}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from fastapi import APIRouter, Depends, Query
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.database.session import get_db
|
from app.database.session import get_db
|
||||||
from app.domain.entities.scheduled_appointment import ScheduledAppointmentEntity
|
from app.domain.entities.scheduled_appointment import ScheduledAppointmentEntity
|
||||||
@ -20,9 +20,11 @@ router = APIRouter()
|
|||||||
async def get_all_scheduled_appointments(
|
async def get_all_scheduled_appointments(
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
scheduled_appointments_service = ScheduledAppointmentsService(db)
|
scheduled_appointments_service = ScheduledAppointmentsService(db)
|
||||||
return await scheduled_appointments_service.get_all_scheduled_appointments()
|
return await scheduled_appointments_service.get_all_scheduled_appointments(start_date=start_date, end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@ -35,9 +37,27 @@ async def get_all_scheduled_appointments_by_doctor_id(
|
|||||||
doctor_id: int,
|
doctor_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
appointments_service = ScheduledAppointmentsService(db)
|
appointments_service = ScheduledAppointmentsService(db)
|
||||||
return await appointments_service.get_scheduled_appointments_by_doctor_id(doctor_id)
|
return await appointments_service.get_scheduled_appointments_by_doctor_id(doctor_id, start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/doctor/{doctor_id}/upcoming/",
|
||||||
|
response_model=list[ScheduledAppointmentEntity],
|
||||||
|
summary="Get upcoming scheduled appointments for doctor",
|
||||||
|
description="Returns the next 5 upcoming scheduled appointments for doctor",
|
||||||
|
)
|
||||||
|
async def get_upcoming_scheduled_appointments_by_doctor_id(
|
||||||
|
doctor_id: int,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
):
|
||||||
|
appointments_service = ScheduledAppointmentsService(db)
|
||||||
|
return await appointments_service.get_upcoming_scheduled_appointments_by_doctor_id(doctor_id)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@ -50,9 +70,12 @@ async def get_all_appointments_by_patient_id(
|
|||||||
patient_id: int,
|
patient_id: int,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
|
start_date: date | None = Query(None, description="Start date for filtering (YYYY-MM-DD)"),
|
||||||
|
end_date: date | None = Query(None, description="End date for filtering (YYYY-MM-DD)"),
|
||||||
):
|
):
|
||||||
appointments_service = ScheduledAppointmentsService(db)
|
appointments_service = ScheduledAppointmentsService(db)
|
||||||
return await appointments_service.get_scheduled_appointments_by_patient_id(patient_id)
|
return await appointments_service.get_scheduled_appointments_by_patient_id(patient_id, start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
|
|||||||
8
api/app/domain/entities/responses/paginated_issue.py
Normal file
8
api/app/domain/entities/responses/paginated_issue.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from app.domain.entities.lens_issues import LensIssueEntity
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedLensIssuesResponseEntity(BaseModel):
|
||||||
|
issues: list[LensIssueEntity]
|
||||||
|
total_count: int
|
||||||
@ -1,8 +1,8 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from starlette import status
|
from starlette import status
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.application.appointment_types_repository import AppointmentTypesRepository
|
from app.application.appointment_types_repository import AppointmentTypesRepository
|
||||||
from app.application.appointments_repository import AppointmentsRepository
|
from app.application.appointments_repository import AppointmentsRepository
|
||||||
@ -22,45 +22,44 @@ class AppointmentsService:
|
|||||||
self.users_repository = UsersRepository(db)
|
self.users_repository = UsersRepository(db)
|
||||||
self.patients_repository = PatientsRepository(db)
|
self.patients_repository = PatientsRepository(db)
|
||||||
|
|
||||||
async def get_all_appointments(self) -> list[AppointmentEntity]:
|
async def get_all_appointments(self, start_date: date | None = None, end_date: date | None = None) -> list[
|
||||||
appointments = await self.appointments_repository.get_all()
|
AppointmentEntity]:
|
||||||
|
appointments = await self.appointments_repository.get_all(start_date=start_date, end_date=end_date)
|
||||||
|
return [self.model_to_entity(appointment) for appointment in appointments]
|
||||||
|
|
||||||
return [
|
async def get_appointments_by_doctor_id(self, doctor_id: int, start_date: date | None = None,
|
||||||
self.model_to_entity(appointment)
|
end_date: date | None = None) -> Optional[list[AppointmentEntity]]:
|
||||||
for appointment in appointments
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_appointments_by_doctor_id(self, doctor_id: int) -> Optional[list[AppointmentEntity]]:
|
|
||||||
doctor = await self.users_repository.get_by_id(doctor_id)
|
doctor = await self.users_repository.get_by_id(doctor_id)
|
||||||
|
|
||||||
if not doctor:
|
if not doctor:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail='Доктор с таким ID не найден',
|
detail='Доктор с таким ID не найден',
|
||||||
)
|
)
|
||||||
|
appointments = await self.appointments_repository.get_by_doctor_id(doctor_id, start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
|
return [self.model_to_entity(appointment) for appointment in appointments]
|
||||||
|
|
||||||
appointments = await self.appointments_repository.get_by_doctor_id(doctor_id)
|
async def get_upcoming_appointments_by_doctor_id(self, doctor_id: int) -> Optional[list[AppointmentEntity]]:
|
||||||
|
doctor = await self.users_repository.get_by_id(doctor_id)
|
||||||
|
if not doctor:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail='Доктор с таким ID не найден',
|
||||||
|
)
|
||||||
|
appointments = await self.appointments_repository.get_upcoming_by_doctor_id(doctor_id)
|
||||||
|
return [self.model_to_entity(appointment) for appointment in appointments]
|
||||||
|
|
||||||
return [
|
async def get_appointments_by_patient_id(self, patient_id: int, start_date: date | None = None,
|
||||||
self.model_to_entity(appointment)
|
end_date: date | None = None) -> Optional[list[AppointmentEntity]]:
|
||||||
for appointment in appointments
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_appointments_by_patient_id(self, patient_id: int) -> Optional[list[AppointmentEntity]]:
|
|
||||||
patient = await self.patients_repository.get_by_id(patient_id)
|
patient = await self.patients_repository.get_by_id(patient_id)
|
||||||
|
|
||||||
if not patient:
|
if not patient:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail='Пациент с таким ID не найден',
|
detail='Пациент с таким ID не найден',
|
||||||
)
|
)
|
||||||
|
appointments = await self.appointments_repository.get_by_patient_id(patient_id, start_date=start_date,
|
||||||
appointments = await self.appointments_repository.get_by_patient_id(patient_id)
|
end_date=end_date)
|
||||||
|
return [self.model_to_entity(appointment) for appointment in appointments]
|
||||||
return [
|
|
||||||
self.model_to_entity(appointment)
|
|
||||||
for appointment in appointments
|
|
||||||
]
|
|
||||||
|
|
||||||
async def create_appointment(self, appointment: AppointmentEntity, doctor_id: int) -> Optional[AppointmentEntity]:
|
async def create_appointment(self, appointment: AppointmentEntity, doctor_id: int) -> Optional[AppointmentEntity]:
|
||||||
patient = await self.patients_repository.get_by_id(appointment.patient_id)
|
patient = await self.patients_repository.get_by_id(appointment.patient_id)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from typing import Optional
|
from datetime import date
|
||||||
|
from typing import Optional, Literal, Tuple
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -22,13 +23,27 @@ class LensIssuesService:
|
|||||||
self.users_repository = UsersRepository(db)
|
self.users_repository = UsersRepository(db)
|
||||||
self.lenses_repository = LensesRepository(db)
|
self.lenses_repository = LensesRepository(db)
|
||||||
|
|
||||||
async def get_all_lens_issues(self) -> list[LensIssueEntity]:
|
async def get_all_lens_issues(
|
||||||
lens_issues = await self.lens_issues_repository.get_all()
|
self,
|
||||||
|
skip: int = 0,
|
||||||
return [
|
limit: int = 10,
|
||||||
self.model_to_entity(lens_issue)
|
search: Optional[str] = None,
|
||||||
for lens_issue in lens_issues
|
sort_order: Literal["asc", "desc"] = "desc",
|
||||||
]
|
start_date: Optional[date] = None,
|
||||||
|
end_date: Optional[date] = None
|
||||||
|
) -> Tuple[list[LensIssueEntity], int]:
|
||||||
|
lens_issues, total_count = await self.lens_issues_repository.get_all(
|
||||||
|
skip=skip,
|
||||||
|
limit=limit,
|
||||||
|
search=search,
|
||||||
|
sort_order=sort_order,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
[self.model_to_entity(lens_issue) for lens_issue in lens_issues],
|
||||||
|
total_count
|
||||||
|
)
|
||||||
|
|
||||||
async def create_lens_issue(self, lens_issue: LensIssueEntity, user_id: int) -> Optional[LensIssueEntity]:
|
async def create_lens_issue(self, lens_issue: LensIssueEntity, user_id: int) -> Optional[LensIssueEntity]:
|
||||||
patient = await self.patient_repository.get_by_id(lens_issue.patient_id)
|
patient = await self.patient_repository.get_by_id(lens_issue.patient_id)
|
||||||
|
|||||||
@ -13,10 +13,12 @@ class PatientsService:
|
|||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.patient_repository = PatientsRepository(db)
|
self.patient_repository = PatientsRepository(db)
|
||||||
|
|
||||||
async def get_all_patients(self, page: int = 1, page_size: int = 10, search: str = None, sort_order: Literal["asc", "desc"] = "asc") -> Tuple[
|
async def get_all_patients(self, page: int = 1, page_size: int = 10, search: str = None,
|
||||||
|
sort_order: Literal["asc", "desc"] = "asc", all_params: bool = False) -> Tuple[
|
||||||
list[PatientEntity], int]:
|
list[PatientEntity], int]:
|
||||||
skip = (page - 1) * page_size
|
skip = (page - 1) * page_size
|
||||||
patients, total_count = await self.patient_repository.get_all(skip=skip, limit=page_size, search=search, sort_order=sort_order)
|
patients, total_count = await self.patient_repository.get_all(skip=skip, limit=page_size, search=search,
|
||||||
|
sort_order=sort_order, all_params=all_params)
|
||||||
return (
|
return (
|
||||||
[self.model_to_entity(patient) for patient in patients],
|
[self.model_to_entity(patient) for patient in patients],
|
||||||
total_count
|
total_count
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from starlette import status
|
from starlette import status
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
from app.application.appointment_types_repository import AppointmentTypesRepository
|
from app.application.appointment_types_repository import AppointmentTypesRepository
|
||||||
from app.application.patients_repository import PatientsRepository
|
from app.application.patients_repository import PatientsRepository
|
||||||
@ -19,54 +19,53 @@ class ScheduledAppointmentsService:
|
|||||||
self.users_repository = UsersRepository(db)
|
self.users_repository = UsersRepository(db)
|
||||||
self.patients_repository = PatientsRepository(db)
|
self.patients_repository = PatientsRepository(db)
|
||||||
|
|
||||||
async def get_all_scheduled_appointments(self) -> list[ScheduledAppointmentEntity]:
|
async def get_all_scheduled_appointments(self, start_date: date | None = None, end_date: date | None = None) -> \
|
||||||
scheduled_appointments = await self.scheduled_appointment_repository.get_all()
|
list[ScheduledAppointmentEntity]:
|
||||||
|
scheduled_appointments = await self.scheduled_appointment_repository.get_all(start_date=start_date,
|
||||||
return [
|
end_date=end_date)
|
||||||
self.model_to_entity(scheduled_appointment)
|
return [self.model_to_entity(scheduled_appointment) for scheduled_appointment in scheduled_appointments]
|
||||||
for scheduled_appointment in scheduled_appointments
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_scheduled_appointments_by_doctor_id(self, doctor_id: int) -> Optional[
|
|
||||||
list[ScheduledAppointmentEntity]
|
|
||||||
]:
|
|
||||||
doctor = self.users_repository.get_by_id(doctor_id)
|
|
||||||
|
|
||||||
|
async def get_scheduled_appointments_by_doctor_id(self, doctor_id: int, start_date: date | None = None,
|
||||||
|
end_date: date | None = None) -> Optional[
|
||||||
|
list[ScheduledAppointmentEntity]]:
|
||||||
|
doctor = await self.users_repository.get_by_id(doctor_id)
|
||||||
if not doctor:
|
if not doctor:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail='Доктор с таким ID не найден',
|
detail='Доктор с таким ID не найден',
|
||||||
)
|
)
|
||||||
|
scheduled_appointments = await self.scheduled_appointment_repository.get_by_doctor_id(doctor_id,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
|
return [self.model_to_entity(scheduled_appointment) for scheduled_appointment in scheduled_appointments]
|
||||||
|
|
||||||
scheduled_appointments = await self.scheduled_appointment_repository.get_by_doctor_id(doctor_id)
|
async def get_upcoming_scheduled_appointments_by_doctor_id(self, doctor_id: int) -> Optional[
|
||||||
|
list[ScheduledAppointmentEntity]]:
|
||||||
|
doctor = await self.users_repository.get_by_id(doctor_id)
|
||||||
|
if not doctor:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail='Доктор с таким ID не найден',
|
||||||
|
)
|
||||||
|
scheduled_appointments = await self.scheduled_appointment_repository.get_upcoming_by_doctor_id(doctor_id)
|
||||||
|
return [self.model_to_entity(scheduled_appointment) for scheduled_appointment in scheduled_appointments]
|
||||||
|
|
||||||
return [
|
async def get_scheduled_appointments_by_patient_id(self, patient_id: int, start_date: date | None = None,
|
||||||
self.model_to_entity(scheduled_appointment)
|
end_date: date | None = None) -> Optional[
|
||||||
for scheduled_appointment in scheduled_appointments
|
list[ScheduledAppointmentEntity]]:
|
||||||
]
|
|
||||||
|
|
||||||
async def get_scheduled_appointments_by_patient_id(self, patient_id: int) -> Optional[
|
|
||||||
list[ScheduledAppointmentEntity]
|
|
||||||
]:
|
|
||||||
patient = await self.patients_repository.get_by_id(patient_id)
|
patient = await self.patients_repository.get_by_id(patient_id)
|
||||||
|
|
||||||
if not patient:
|
if not patient:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail='Пациент с таким ID не найден',
|
detail='Пациент с таким ID не найден',
|
||||||
)
|
)
|
||||||
|
scheduled_appointments = await self.scheduled_appointment_repository.get_by_patient_id(patient_id,
|
||||||
scheduled_appointments = await self.scheduled_appointment_repository.get_by_patient_id(patient_id)
|
start_date=start_date,
|
||||||
|
end_date=end_date)
|
||||||
return [
|
return [self.model_to_entity(scheduled_appointment) for scheduled_appointment in scheduled_appointments]
|
||||||
self.model_to_entity(scheduled_appointment)
|
|
||||||
for scheduled_appointment in scheduled_appointments
|
|
||||||
]
|
|
||||||
|
|
||||||
async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity, doctor_id: int) -> \
|
async def create_scheduled_appointment(self, scheduled_appointment: ScheduledAppointmentEntity, doctor_id: int) -> \
|
||||||
Optional[
|
Optional[ScheduledAppointmentEntity]:
|
||||||
ScheduledAppointmentEntity
|
|
||||||
]:
|
|
||||||
patient = await self.patients_repository.get_by_id(scheduled_appointment.patient_id)
|
patient = await self.patients_repository.get_by_id(scheduled_appointment.patient_id)
|
||||||
|
|
||||||
if not patient:
|
if not patient:
|
||||||
@ -99,7 +98,7 @@ class ScheduledAppointmentsService:
|
|||||||
|
|
||||||
return self.model_to_entity(scheduled_appointment_model)
|
return self.model_to_entity(scheduled_appointment_model)
|
||||||
|
|
||||||
async def cancel_scheduled_appointment(self, scheduled_appointment_id: int, doctor_id):
|
async def cancel_scheduled_appointment(self, scheduled_appointment_id: int, doctor_id: int):
|
||||||
scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)
|
scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)
|
||||||
|
|
||||||
if not scheduled_appointment_model:
|
if not scheduled_appointment_model:
|
||||||
@ -129,11 +128,9 @@ class ScheduledAppointmentsService:
|
|||||||
|
|
||||||
return self.model_to_entity(scheduled_appointment_model)
|
return self.model_to_entity(scheduled_appointment_model)
|
||||||
|
|
||||||
async def update_scheduled_appointment(
|
async def update_scheduled_appointment(self, scheduled_appointment_id: int,
|
||||||
self,
|
scheduled_appointment: ScheduledAppointmentEntity) -> Optional[
|
||||||
scheduled_appointment_id: int,
|
ScheduledAppointmentEntity]:
|
||||||
scheduled_appointment: ScheduledAppointmentEntity
|
|
||||||
) -> Optional[ScheduledAppointmentEntity]:
|
|
||||||
scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)
|
scheduled_appointment_model = await self.scheduled_appointment_repository.get_by_id(scheduled_appointment_id)
|
||||||
|
|
||||||
if not scheduled_appointment_model:
|
if not scheduled_appointment_model:
|
||||||
|
|||||||
@ -7,10 +7,17 @@ export const appointmentsApi = createApi({
|
|||||||
tagTypes: ['Appointment'],
|
tagTypes: ['Appointment'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getAppointments: builder.query({
|
getAppointments: builder.query({
|
||||||
query: (doctor_id) => `/appointments/doctor/${doctor_id}/`,
|
query: ({doctor_id, start_date, end_date}) => ({
|
||||||
|
url: `/appointments/doctor/${doctor_id}/`,
|
||||||
|
params: {start_date, end_date},
|
||||||
|
}),
|
||||||
providesTags: ['Appointment'],
|
providesTags: ['Appointment'],
|
||||||
refetchOnMountOrArgChange: 5,
|
refetchOnMountOrArgChange: 5,
|
||||||
}),
|
}),
|
||||||
|
getUpcomingAppointments: builder.query({
|
||||||
|
query: (doctor_id) => `/appointments/doctor/${doctor_id}/upcoming/`,
|
||||||
|
providesTags: ['Appointment'],
|
||||||
|
}),
|
||||||
getByPatientId: builder.query({
|
getByPatientId: builder.query({
|
||||||
query: (id) => `/appointments/patient/${id}/`,
|
query: (id) => `/appointments/patient/${id}/`,
|
||||||
providesTags: ['Appointment'],
|
providesTags: ['Appointment'],
|
||||||
@ -37,6 +44,7 @@ export const appointmentsApi = createApi({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetAppointmentsQuery,
|
useGetAppointmentsQuery,
|
||||||
|
useGetUpcomingAppointmentsQuery,
|
||||||
useGetByPatientIdQuery,
|
useGetByPatientIdQuery,
|
||||||
useCreateAppointmentMutation,
|
useCreateAppointmentMutation,
|
||||||
useUpdateAppointmentMutation,
|
useUpdateAppointmentMutation,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import {createApi} from "@reduxjs/toolkit/query/react";
|
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||||
import {baseQueryWithAuth} from "./baseQuery.js";
|
import { baseQueryWithAuth } from "./baseQuery.js";
|
||||||
|
|
||||||
|
|
||||||
export const lensIssuesApi = createApi({
|
export const lensIssuesApi = createApi({
|
||||||
reducerPath: 'lensIssuesApi',
|
reducerPath: 'lensIssuesApi',
|
||||||
@ -8,22 +7,49 @@ export const lensIssuesApi = createApi({
|
|||||||
tagTypes: ['LensIssues'],
|
tagTypes: ['LensIssues'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getLensIssues: builder.query({
|
getLensIssues: builder.query({
|
||||||
query: () => '/lens_issues/',
|
query: ({ page, pageSize, search, sortOrder, startDate, endDate }) => ({
|
||||||
|
url: '/lens_issues/',
|
||||||
|
params: {
|
||||||
|
page,
|
||||||
|
page_size: pageSize,
|
||||||
|
search: search || undefined,
|
||||||
|
sort_order: sortOrder || undefined,
|
||||||
|
start_date: startDate || undefined,
|
||||||
|
end_date: endDate || undefined,
|
||||||
|
},
|
||||||
|
}),
|
||||||
providesTags: ['LensIssues'],
|
providesTags: ['LensIssues'],
|
||||||
refetchOnMountOrArgChange: 5
|
transformResponse: (response) => {
|
||||||
|
if (!response) {
|
||||||
|
console.warn('Empty lens issues API response:', response);
|
||||||
|
return { issues: [], total_count: 0 };
|
||||||
|
}
|
||||||
|
if (Array.isArray(response.results) && typeof response.count === 'number') {
|
||||||
|
return { issues: response.results, total_count: response.count };
|
||||||
|
}
|
||||||
|
if (Array.isArray(response.issues) && typeof response.total_count === 'number') {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
console.warn('Unexpected lens issues API response:', response);
|
||||||
|
return { issues: [], total_count: 0 };
|
||||||
|
},
|
||||||
|
transformErrorResponse: (response) => {
|
||||||
|
console.error('Lens issues API error:', response);
|
||||||
|
return response.data?.detail || 'Unknown error';
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
addLensIssues: builder.mutation({
|
addLensIssues: builder.mutation({
|
||||||
query: (lensIssues) => ({
|
query: (lensIssues) => ({
|
||||||
url: `/lens_issues/`,
|
url: '/lens_issues/',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: lensIssues
|
body: lensIssues,
|
||||||
}),
|
}),
|
||||||
invalidatesTags: ['LensIssues']
|
invalidatesTags: ['LensIssues'],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetLensIssuesQuery,
|
useGetLensIssuesQuery,
|
||||||
useAddLensIssuesMutation
|
useAddLensIssuesMutation,
|
||||||
} = lensIssuesApi;
|
} = lensIssuesApi;
|
||||||
@ -18,6 +18,20 @@ export const patientsApi = createApi({
|
|||||||
}),
|
}),
|
||||||
providesTags: ['Patients'],
|
providesTags: ['Patients'],
|
||||||
}),
|
}),
|
||||||
|
getAllPatients: builder.query({
|
||||||
|
query: () => ({
|
||||||
|
url: '/patients/',
|
||||||
|
params: { all_params: true },
|
||||||
|
}),
|
||||||
|
providesTags: ['Patient'],
|
||||||
|
transformResponse: (response) => {
|
||||||
|
if (!response || !Array.isArray(response.patients)) {
|
||||||
|
console.warn('Unexpected patients API response:', response);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return response.patients;
|
||||||
|
},
|
||||||
|
}),
|
||||||
addPatient: builder.mutation({
|
addPatient: builder.mutation({
|
||||||
query: (patient) => ({
|
query: (patient) => ({
|
||||||
url: '/patients/',
|
url: '/patients/',
|
||||||
@ -46,6 +60,7 @@ export const patientsApi = createApi({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetPatientsQuery,
|
useGetPatientsQuery,
|
||||||
|
useGetAllPatientsQuery,
|
||||||
useAddPatientMutation,
|
useAddPatientMutation,
|
||||||
useUpdatePatientMutation,
|
useUpdatePatientMutation,
|
||||||
useDeletePatientMutation,
|
useDeletePatientMutation,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {createApi} from "@reduxjs/toolkit/query/react";
|
import { createApi } from "@reduxjs/toolkit/query/react";
|
||||||
import {baseQueryWithAuth} from "./baseQuery.js";
|
import { baseQueryWithAuth } from "./baseQuery.js";
|
||||||
|
|
||||||
export const scheduledAppointmentsApi = createApi({
|
export const scheduledAppointmentsApi = createApi({
|
||||||
reducerPath: 'scheduledAppointmentsApi',
|
reducerPath: 'scheduledAppointmentsApi',
|
||||||
@ -7,7 +7,14 @@ export const scheduledAppointmentsApi = createApi({
|
|||||||
tagTypes: ['ScheduledAppointment'],
|
tagTypes: ['ScheduledAppointment'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getScheduledAppointments: builder.query({
|
getScheduledAppointments: builder.query({
|
||||||
query: (doctor_id) => `/scheduled_appointments/doctor/${doctor_id}/`,
|
query: ({ doctor_id, start_date, end_date }) => ({
|
||||||
|
url: `/scheduled_appointments/doctor/${doctor_id}/`,
|
||||||
|
params: { start_date, end_date },
|
||||||
|
}),
|
||||||
|
providesTags: ['ScheduledAppointment'],
|
||||||
|
}),
|
||||||
|
getUpcomingScheduledAppointments: builder.query({
|
||||||
|
query: (doctor_id) => `/scheduled_appointments/doctor/${doctor_id}/upcoming/`,
|
||||||
providesTags: ['ScheduledAppointment'],
|
providesTags: ['ScheduledAppointment'],
|
||||||
}),
|
}),
|
||||||
createScheduledAppointment: builder.mutation({
|
createScheduledAppointment: builder.mutation({
|
||||||
@ -19,7 +26,7 @@ export const scheduledAppointmentsApi = createApi({
|
|||||||
invalidatesTags: ['ScheduledAppointment'],
|
invalidatesTags: ['ScheduledAppointment'],
|
||||||
}),
|
}),
|
||||||
updateScheduledAppointment: builder.mutation({
|
updateScheduledAppointment: builder.mutation({
|
||||||
query: ({id, data}) => ({
|
query: ({ id, data }) => ({
|
||||||
url: `/scheduled_appointments/${id}/`,
|
url: `/scheduled_appointments/${id}/`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: data,
|
body: data,
|
||||||
@ -38,6 +45,7 @@ export const scheduledAppointmentsApi = createApi({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
useGetScheduledAppointmentsQuery,
|
useGetScheduledAppointmentsQuery,
|
||||||
|
useGetUpcomingScheduledAppointmentsQuery,
|
||||||
useCreateScheduledAppointmentMutation,
|
useCreateScheduledAppointmentMutation,
|
||||||
useUpdateScheduledAppointmentMutation,
|
useUpdateScheduledAppointmentMutation,
|
||||||
useCancelScheduledAppointmentMutation,
|
useCancelScheduledAppointmentMutation,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
import {useGetAllPatientsQuery, useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
||||||
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
||||||
import {
|
import {
|
||||||
useCreateAppointmentMutation,
|
useCreateAppointmentMutation,
|
||||||
@ -11,7 +11,7 @@ const useAppointmentFormModal = () => {
|
|||||||
data: patients = [],
|
data: patients = [],
|
||||||
isLoading: isLoadingPatients,
|
isLoading: isLoadingPatients,
|
||||||
isError: isErrorPatients,
|
isError: isErrorPatients,
|
||||||
} = useGetPatientsQuery(undefined);
|
} = useGetAllPatientsQuery(undefined);
|
||||||
const {
|
const {
|
||||||
data: appointmentTypes = [],
|
data: appointmentTypes = [],
|
||||||
isLoading: isLoadingAppointmentTypes,
|
isLoading: isLoadingAppointmentTypes,
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import dayjs from "dayjs";
|
|||||||
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
||||||
import {Grid} from "antd";
|
import {Grid} from "antd";
|
||||||
import {useUploadAppointmentFileMutation} from "../../../Api/appointmentFilesApi.js";
|
import {useUploadAppointmentFileMutation} from "../../../Api/appointmentFilesApi.js";
|
||||||
import Compressor from 'compressorjs';
|
|
||||||
|
|
||||||
const {useBreakpoint} = Grid;
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
@ -30,9 +29,20 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
|
|
||||||
const [uploadAppointmentFile, {isLoading: isUploadingFile}] = useUploadAppointmentFileMutation();
|
const [uploadAppointmentFile, {isLoading: isUploadingFile}] = useUploadAppointmentFileMutation();
|
||||||
|
|
||||||
const {data: appointments = []} = useGetAppointmentsQuery(userData.id, {
|
const startDate = appointmentDate.startOf('month').utc().format('YYYY-MM-DD');
|
||||||
pollingInterval: 20000,
|
const endDate = appointmentDate.endOf('month').utc().format('YYYY-MM-DD');
|
||||||
});
|
|
||||||
|
const {
|
||||||
|
data: appointments = [],
|
||||||
|
isLoading: isLoadingAppointments,
|
||||||
|
isError: isErrorAppointments,
|
||||||
|
} = useGetAppointmentsQuery(
|
||||||
|
{doctor_id: userData.id, start_date: startDate, end_date: endDate},
|
||||||
|
{
|
||||||
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: previousAppointments = [],
|
data: previousAppointments = [],
|
||||||
@ -43,6 +53,16 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
skip: !selectedPatient,
|
skip: !selectedPatient,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isErrorAppointments) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Ошибка',
|
||||||
|
description: 'Ошибка загрузки приемов.',
|
||||||
|
placement: 'topRight',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isErrorAppointments]);
|
||||||
|
|
||||||
const blockStepStyle = {marginBottom: 16};
|
const blockStepStyle = {marginBottom: 16};
|
||||||
const searchInputStyle = {marginBottom: 16};
|
const searchInputStyle = {marginBottom: 16};
|
||||||
const chooseContainerStyle = {maxHeight: 400, overflowY: "auto"};
|
const chooseContainerStyle = {maxHeight: 400, overflowY: "auto"};
|
||||||
@ -180,6 +200,7 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
appointment_datetime: dayjs(scheduledData.appointment_datetime),
|
appointment_datetime: dayjs(scheduledData.appointment_datetime),
|
||||||
results: scheduledData.results || "",
|
results: scheduledData.results || "",
|
||||||
});
|
});
|
||||||
|
setAppointmentDate(dayjs(scheduledData.appointment_datetime));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@ -219,9 +240,14 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
setFormValues({});
|
setFormValues({});
|
||||||
setIsDrawerVisible(false);
|
setIsDrawerVisible(false);
|
||||||
setDraftFiles([]);
|
setDraftFiles([]);
|
||||||
|
setAppointmentDate(dayjs(new Date())); // Сбрасываем дату
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetAppointmentDate = (date) => {
|
||||||
|
setAppointmentDate(date);
|
||||||
|
form.setFieldsValue({appointment_datetime: date}); // Синхронизируем форму
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSetAppointmentDate = (date) => setAppointmentDate(date);
|
|
||||||
const modalWidth = useMemo(() => (screenXS ? 700 : "90%"), [screenXS]);
|
const modalWidth = useMemo(() => (screenXS ? 700 : "90%"), [screenXS]);
|
||||||
|
|
||||||
const showDrawer = () => {
|
const showDrawer = () => {
|
||||||
@ -330,8 +356,8 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating appointment:", error);
|
console.error("Error creating appointment:", error);
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка загрузки файла",
|
message: "Ошибка создания приема",
|
||||||
description: `Не удалось загрузить файл ${JSON.stringify(error.data?.detail || error.data || error.message || "Неизвестная ошибка", null, 2)}`,
|
description: `Не удалось создать прием: ${JSON.stringify(error.data?.detail || error.data || error.message || "Неизвестная ошибка", null, 2)}`,
|
||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -405,6 +431,8 @@ const useAppointmentFormModalUI = (createAppointment, patients, cancelAppointmen
|
|||||||
handleAddFile,
|
handleAddFile,
|
||||||
handleRemoveFile,
|
handleRemoveFile,
|
||||||
isUploadingFile,
|
isUploadingFile,
|
||||||
|
isLoadingAppointments,
|
||||||
|
isErrorAppointments,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
import {useGetAllPatientsQuery} from "../../../Api/patientsApi.js";
|
||||||
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
||||||
import {useCreateScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
import {useCreateScheduledAppointmentMutation} from "../../../Api/scheduledAppointmentsApi.js";
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ const useScheduledAppointmentFormModal = () => {
|
|||||||
data: patients = [],
|
data: patients = [],
|
||||||
isLoading: isLoadingPatients,
|
isLoading: isLoadingPatients,
|
||||||
isError: isErrorPatients,
|
isError: isErrorPatients,
|
||||||
} = useGetPatientsQuery(undefined, {
|
} = useGetAllPatientsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {Badge, Button, Col, FloatButton, List, Result, Row, Space, Tag, Typography} from "antd";
|
import { Badge, Button, FloatButton, List, Result, Row, Space, Tag, Typography } from "antd";
|
||||||
import {Splitter} from "antd";
|
import { Splitter } from "antd";
|
||||||
import {
|
import {
|
||||||
CalendarOutlined,
|
CalendarOutlined,
|
||||||
MenuFoldOutlined,
|
MenuFoldOutlined,
|
||||||
@ -8,38 +8,46 @@ import {
|
|||||||
ClockCircleOutlined,
|
ClockCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
import AppointmentsCalendarTab from "./Components/AppointmentCalendarTab/AppointmentsCalendarTab.jsx";
|
||||||
import useAppointmentsUI from "./useAppointmentsUI.js";
|
|
||||||
import useAppointments from "./useAppointments.js";
|
import useAppointments from "./useAppointments.js";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
|
import AppointmentFormModal from "../../Dummies/AppointmentFormModal/AppointmentFormModal.jsx";
|
||||||
import {useDispatch} from "react-redux";
|
|
||||||
import {
|
|
||||||
openModal,
|
|
||||||
setSelectedAppointment,
|
|
||||||
setSelectedScheduledAppointment
|
|
||||||
} from "../../../Redux/Slices/appointmentsSlice.js";
|
|
||||||
import AppointmentViewModal from "../../Dummies/AppointmentViewModal/AppointmentViewModal.jsx";
|
import AppointmentViewModal from "../../Dummies/AppointmentViewModal/AppointmentViewModal.jsx";
|
||||||
import ScheduledAppointmentFormModal
|
import ScheduledAppointmentFormModal from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
||||||
from "../../Dummies/ScheduledAppintmentFormModal/ScheduledAppointmentFormModal.jsx";
|
import ScheduledAppointmentsViewModal from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
||||||
import ScheduledAppointmentsViewModal
|
|
||||||
from "../../Widgets/ScheduledAppointmentsViewModal/ScheduledAppointmentsViewModal.jsx";
|
|
||||||
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
|
import AppointmentsListModal from "./Components/AppointmentsListModal/AppointmentsListModal.jsx";
|
||||||
|
|
||||||
const AppointmentsPage = () => {
|
const AppointmentsPage = () => {
|
||||||
const appointmentsData = useAppointments();
|
const {
|
||||||
const appointmentsPageUI = useAppointmentsUI(appointmentsData.appointments, appointmentsData.scheduledAppointments);
|
patients, // Добавляем
|
||||||
const dispatch = useDispatch();
|
appointments, // Добавляем
|
||||||
|
scheduledAppointments, // Добавляем
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
collapsed,
|
||||||
|
siderWidth,
|
||||||
|
showSplitterPanel,
|
||||||
|
siderButtonText,
|
||||||
|
splitterStyle,
|
||||||
|
splitterContentPanelStyle,
|
||||||
|
splitterSiderPanelStyle,
|
||||||
|
siderTitleStyle,
|
||||||
|
siderButtonContainerStyle,
|
||||||
|
siderButtonStyle,
|
||||||
|
badgeTextStyle,
|
||||||
|
upcomingEvents,
|
||||||
|
handleToggleSider,
|
||||||
|
handleHoverSider,
|
||||||
|
handleLeaveSider,
|
||||||
|
handleSetSiderWidth,
|
||||||
|
openCreateScheduledAppointmentModal,
|
||||||
|
currentMonth,
|
||||||
|
handleMonthChange,
|
||||||
|
handleEventClick,
|
||||||
|
openCreateAppointmentModal,
|
||||||
|
} = useAppointments();
|
||||||
|
|
||||||
const handleEventClick = (event) => {
|
if (isError) return (
|
||||||
if (event.appointment_datetime) {
|
|
||||||
dispatch(setSelectedAppointment(event));
|
|
||||||
} else {
|
|
||||||
dispatch(setSelectedScheduledAppointment(event));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (appointmentsData.isError) return (
|
|
||||||
<Result
|
<Result
|
||||||
status="error"
|
status="error"
|
||||||
title="Ошибка"
|
title="Ошибка"
|
||||||
@ -49,66 +57,59 @@ const AppointmentsPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={1}><CalendarOutlined/> Приемы</Typography.Title>
|
<Typography.Title level={1}><CalendarOutlined /> Приемы</Typography.Title>
|
||||||
{appointmentsData.isLoading ? (
|
{isLoading ? (
|
||||||
<LoadingIndicator/>
|
<LoadingIndicator />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Row justify="end" style={{marginBottom: 10, marginRight: "2.4rem"}}>
|
<Row justify="end" style={{ marginBottom: 10, marginRight: "2.4rem" }}>
|
||||||
<Space direction={"vertical"}>
|
<Space direction={"vertical"}>
|
||||||
<Tag color={"blue"} style={{width: "100%"}}>
|
<Tag color={"blue"} style={{ width: "100%" }}>
|
||||||
<Badge status={"processing"}
|
<Badge status={"processing"}
|
||||||
text={
|
text={<span style={badgeTextStyle}>Запланированный прием</span>} />
|
||||||
<span style={appointmentsPageUI.badgeTextStyle}>
|
|
||||||
Запланированный прием
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag color={"green"} style={{width: "100%"}}>
|
<Tag color={"green"} style={{ width: "100%" }}>
|
||||||
<Badge status={"success"}
|
<Badge status={"success"}
|
||||||
text={
|
text={<span style={badgeTextStyle}>Прошедший прием</span>} />
|
||||||
<span style={appointmentsPageUI.badgeTextStyle}>
|
|
||||||
Прошедший прием
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tag>
|
</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
</Row>
|
</Row>
|
||||||
<Splitter
|
<Splitter
|
||||||
style={appointmentsPageUI.splitterStyle}
|
style={splitterStyle}
|
||||||
min={200}
|
min={200}
|
||||||
max={400}
|
max={400}
|
||||||
initial={appointmentsPageUI.siderWidth}
|
initial={siderWidth}
|
||||||
onChange={appointmentsPageUI.setSiderWidth}
|
onChange={handleSetSiderWidth}
|
||||||
>
|
>
|
||||||
<Splitter.Panel
|
<Splitter.Panel
|
||||||
style={appointmentsPageUI.splitterContentPanelStyle}
|
style={splitterContentPanelStyle}
|
||||||
defaultSize="80%"
|
defaultSize="80%"
|
||||||
min="25%"
|
min="25%"
|
||||||
max="90%"
|
max="90%"
|
||||||
>
|
>
|
||||||
<AppointmentsCalendarTab/>
|
<AppointmentsCalendarTab
|
||||||
|
currentMonth={currentMonth}
|
||||||
|
onMonthChange={handleMonthChange}
|
||||||
|
appointments={appointments} // Добавляем
|
||||||
|
scheduledAppointments={scheduledAppointments} // Добавляем
|
||||||
|
/>
|
||||||
</Splitter.Panel>
|
</Splitter.Panel>
|
||||||
|
{showSplitterPanel && (
|
||||||
{appointmentsPageUI.showSplitterPanel && (
|
|
||||||
<Splitter.Panel
|
<Splitter.Panel
|
||||||
style={appointmentsPageUI.splitterSiderPanelStyle}
|
style={splitterSiderPanelStyle}
|
||||||
defaultSize="20%"
|
defaultSize="20%"
|
||||||
min="20%"
|
min="20%"
|
||||||
max="75%"
|
max="75%"
|
||||||
>
|
>
|
||||||
<Typography.Title level={3} style={appointmentsPageUI.siderTitleStyle}>
|
<Typography.Title level={3} style={siderTitleStyle}>
|
||||||
Предстоящие события
|
Предстоящие события
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
{appointmentsPageUI.upcomingEvents.length ? (
|
{upcomingEvents.length ? (
|
||||||
<List
|
<List
|
||||||
dataSource={appointmentsPageUI.upcomingEvents.sort((a, b) =>
|
dataSource={upcomingEvents.sort((a, b) =>
|
||||||
dayjs(a.appointment_datetime || a.scheduled_datetime).diff(
|
dayjs(a.appointment_datetime || a.scheduled_datetime).diff(
|
||||||
dayjs(b.appointment_datetime || b.scheduled_datetime)
|
dayjs(b.appointment_datetime || b.scheduled_datetime)
|
||||||
)
|
))}
|
||||||
)}
|
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
onClick={() => handleEventClick(item)}
|
onClick={() => handleEventClick(item)}
|
||||||
@ -126,9 +127,9 @@ const AppointmentsPage = () => {
|
|||||||
<Space direction="vertical" size={2}>
|
<Space direction="vertical" size={2}>
|
||||||
<Space>
|
<Space>
|
||||||
{item.appointment_datetime ? (
|
{item.appointment_datetime ? (
|
||||||
<ClockCircleOutlined style={{color: "#52c41a"}}/>
|
<ClockCircleOutlined style={{ color: "#52c41a" }} />
|
||||||
) : (
|
) : (
|
||||||
<CalendarOutlined style={{color: "#1890ff"}}/>
|
<CalendarOutlined style={{ color: "#1890ff" }} />
|
||||||
)}
|
)}
|
||||||
<Typography.Text strong>
|
<Typography.Text strong>
|
||||||
{dayjs(item.appointment_datetime || item.scheduled_datetime).format('DD.MM.YYYY HH:mm')}
|
{dayjs(item.appointment_datetime || item.scheduled_datetime).format('DD.MM.YYYY HH:mm')}
|
||||||
@ -155,43 +156,43 @@ const AppointmentsPage = () => {
|
|||||||
)}
|
)}
|
||||||
</Splitter>
|
</Splitter>
|
||||||
<div
|
<div
|
||||||
style={appointmentsPageUI.siderButtonContainerStyle}
|
style={siderButtonContainerStyle}
|
||||||
onMouseEnter={appointmentsPageUI.handleHoverSider}
|
onMouseEnter={handleHoverSider}
|
||||||
onMouseLeave={appointmentsPageUI.handleLeaveSider}
|
onMouseLeave={handleLeaveSider}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={appointmentsPageUI.handleToggleSider}
|
onClick={handleToggleSider}
|
||||||
icon={appointmentsPageUI.collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
|
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
style={appointmentsPageUI.siderButtonStyle}
|
style={siderButtonStyle}
|
||||||
>
|
>
|
||||||
{appointmentsPageUI.siderButtonText}
|
{siderButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<FloatButton.Group
|
<FloatButton.Group
|
||||||
placement={"left"}
|
placement={"left"}
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined/>}
|
icon={<PlusOutlined />}
|
||||||
tooltip="Создать"
|
tooltip="Создать"
|
||||||
>
|
>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<PlusOutlined/>}
|
icon={<PlusOutlined />}
|
||||||
onClick={() => dispatch(openModal())}
|
onClick={openCreateAppointmentModal}
|
||||||
tooltip="Прием"
|
tooltip="Прием"
|
||||||
/>
|
/>
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<CalendarOutlined/>}
|
icon={<CalendarOutlined />}
|
||||||
onClick={appointmentsPageUI.openCreateScheduledAppointmentModal}
|
onClick={openCreateScheduledAppointmentModal}
|
||||||
tooltip="Запланированный прием"
|
tooltip="Запланированный прием"
|
||||||
/>
|
/>
|
||||||
</FloatButton.Group>
|
</FloatButton.Group>
|
||||||
|
|
||||||
<AppointmentFormModal/>
|
<AppointmentFormModal />
|
||||||
<AppointmentViewModal/>
|
<AppointmentViewModal />
|
||||||
<ScheduledAppointmentFormModal/>
|
<ScheduledAppointmentFormModal />
|
||||||
<ScheduledAppointmentsViewModal/>
|
<ScheduledAppointmentsViewModal />
|
||||||
<AppointmentsListModal/>
|
<AppointmentsListModal />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,25 +1,21 @@
|
|||||||
import { Calendar } from "antd";
|
import {Calendar} from "antd";
|
||||||
import "dayjs/locale/ru";
|
import "dayjs/locale/ru";
|
||||||
import CalendarCell from "../CalendarCell/CalendarCell.jsx";
|
import CalendarCell from "../CalendarCell/CalendarCell.jsx";
|
||||||
import useAppointments from "../../useAppointments.js";
|
|
||||||
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
|
import useAppointmentCalendarUI from "./useAppointmentCalendarUI.js";
|
||||||
import AppointmentsListModal from "../AppointmentsListModal/AppointmentsListModal.jsx";
|
import AppointmentsListModal from "../AppointmentsListModal/AppointmentsListModal.jsx";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const AppointmentsCalendarTab = () => {
|
const AppointmentsCalendarTab = ({currentMonth, onMonthChange, appointments, scheduledAppointments}) => {
|
||||||
const appointmentsData = useAppointments();
|
const appointmentsCalendarUI = useAppointmentCalendarUI(appointments, scheduledAppointments);
|
||||||
const appointmentsCalendarUI = useAppointmentCalendarUI(
|
|
||||||
appointmentsData.appointments,
|
|
||||||
appointmentsData.scheduledAppointments
|
|
||||||
);
|
|
||||||
|
|
||||||
const dateCellRender = (value) => {
|
const dateCellRender = (value) => {
|
||||||
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
const appointmentsForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
||||||
appointmentsData.appointments,
|
appointments,
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
const scheduledForDate = appointmentsCalendarUI.getAppointmentsByListAndDate(
|
||||||
appointmentsData.scheduledAppointments,
|
scheduledAppointments,
|
||||||
value,
|
value,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -43,14 +39,26 @@ const AppointmentsCalendarTab = () => {
|
|||||||
<div style={appointmentsCalendarUI.calendarContainerStyle}>
|
<div style={appointmentsCalendarUI.calendarContainerStyle}>
|
||||||
<Calendar
|
<Calendar
|
||||||
fullscreen={appointmentsCalendarUI.fullScreenCalendar}
|
fullscreen={appointmentsCalendarUI.fullScreenCalendar}
|
||||||
value={appointmentsCalendarUI.selectedDate}
|
value={currentMonth} // Используем currentMonth вместо selectedDate
|
||||||
onSelect={appointmentsCalendarUI.onSelect}
|
onSelect={appointmentsCalendarUI.onSelect}
|
||||||
onPanelChange={appointmentsCalendarUI.onPanelChange}
|
onPanelChange={(value, mode) => {
|
||||||
|
appointmentsCalendarUI.onPanelChange(value, mode);
|
||||||
|
if (mode === "month") {
|
||||||
|
onMonthChange(value); // Вызываем onMonthChange при смене месяца
|
||||||
|
}
|
||||||
|
}}
|
||||||
cellRender={dateCellRender}
|
cellRender={dateCellRender}
|
||||||
/>
|
/>
|
||||||
<AppointmentsListModal />
|
<AppointmentsListModal/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AppointmentsCalendarTab.propTypes = {
|
||||||
|
currentMonth: PropTypes.object.isRequired,
|
||||||
|
onMonthChange: PropTypes.func.isRequired,
|
||||||
|
appointments: PropTypes.array.isRequired,
|
||||||
|
scheduledAppointments: PropTypes.array.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default AppointmentsCalendarTab;
|
export default AppointmentsCalendarTab;
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import {useCreateAppointmentMutation, useUpdateAppointmentMutation} from "../../../../../Api/appointmentsApi.js";
|
|
||||||
import {
|
|
||||||
useCreateScheduledAppointmentMutation,
|
|
||||||
useUpdateScheduledAppointmentMutation
|
|
||||||
} from "../../../../../Api/scheduledAppointmentsApi.js";
|
|
||||||
|
|
||||||
|
|
||||||
const useAppointmentCalendar = () => {
|
|
||||||
const [createAppointment] = useCreateAppointmentMutation();
|
|
||||||
const [updateAppointment] = useUpdateAppointmentMutation();
|
|
||||||
const [createScheduledAppointment] = useCreateScheduledAppointmentMutation();
|
|
||||||
const [updateScheduledAppointment] = useUpdateScheduledAppointmentMutation();
|
|
||||||
};
|
|
||||||
@ -1,39 +1,168 @@
|
|||||||
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmentsApi.js";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
import { notification } from "antd";
|
||||||
import {notification} from "antd";
|
import { Grid } from "antd";
|
||||||
import {useEffect} from "react";
|
import {
|
||||||
import {useSelector} from "react-redux";
|
useGetAppointmentsQuery,
|
||||||
|
useGetUpcomingAppointmentsQuery,
|
||||||
|
} from "../../../Api/appointmentsApi.js";
|
||||||
|
import { useGetAllPatientsQuery } from "../../../Api/patientsApi.js";
|
||||||
|
import {
|
||||||
|
openModal,
|
||||||
|
openScheduledModal,
|
||||||
|
setHovered,
|
||||||
|
setSelectedAppointment,
|
||||||
|
setSelectedScheduledAppointment,
|
||||||
|
toggleSider
|
||||||
|
} from "../../../Redux/Slices/appointmentsSlice.js";
|
||||||
|
import {
|
||||||
|
useGetScheduledAppointmentsQuery,
|
||||||
|
useGetUpcomingScheduledAppointmentsQuery,
|
||||||
|
} from "../../../Api/scheduledAppointmentsApi.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
|
const { useBreakpoint } = Grid;
|
||||||
|
|
||||||
const useAppointments = () => {
|
const useAppointments = () => {
|
||||||
const {
|
const dispatch = useDispatch();
|
||||||
userData
|
const { userData } = useSelector(state => state.auth);
|
||||||
} = useSelector(state => state.auth);
|
const { collapsed, siderWidth, hovered, selectedAppointment } = useSelector(state => state.appointmentsUI);
|
||||||
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
|
const [currentMonth, setCurrentMonth] = useState(dayjs().startOf('month'));
|
||||||
|
|
||||||
|
const startDate = currentMonth.startOf('month').format('YYYY-MM-DD');
|
||||||
|
const endDate = currentMonth.endOf('month').format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
const handleMonthChange = (newMonth) => {
|
||||||
|
setCurrentMonth(dayjs(newMonth).startOf('month'));
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: appointments = [],
|
data: appointments = [],
|
||||||
isLoading: isLoadingAppointments,
|
isLoading: isLoadingAppointments,
|
||||||
isError: isErrorAppointments,
|
isError: isErrorAppointments,
|
||||||
} = useGetAppointmentsQuery((userData.id), {
|
} = useGetAppointmentsQuery({ doctor_id: userData.id, start_date: startDate, end_date: endDate }, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: scheduledAppointments = [],
|
data: scheduledAppointments = [],
|
||||||
isLoading: isLoadingScheduledAppointments,
|
isLoading: isLoadingScheduledAppointments,
|
||||||
isError: isErrorScheduledAppointments,
|
isError: isErrorScheduledAppointments,
|
||||||
} = useGetScheduledAppointmentsQuery((userData.id), {
|
} = useGetScheduledAppointmentsQuery({ doctor_id: userData.id, start_date: startDate, end_date: endDate }, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: patients = [],
|
data: patients = [],
|
||||||
isLoading: isLoadingPatients,
|
isLoading: isLoadingPatients,
|
||||||
isError: isErrorPatients,
|
isError: isErrorPatients,
|
||||||
} = useGetPatientsQuery(undefined, {
|
} = useGetAllPatientsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: upcomingAppointments = [],
|
||||||
|
isLoading: isLoadingUpcomingAppointments,
|
||||||
|
isError: isErrorUpcomingAppointments,
|
||||||
|
} = useGetUpcomingAppointmentsQuery(userData.id, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: upcomingScheduledAppointments = [],
|
||||||
|
isLoading: isLoadingUpcomingScheduledAppointments,
|
||||||
|
isError: isErrorUpcomingScheduledAppointments,
|
||||||
|
} = useGetUpcomingScheduledAppointmentsQuery(userData.id, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [localSiderWidth, setLocalSiderWidth] = useState(siderWidth);
|
||||||
|
|
||||||
|
const splitterStyle = { flex: 1 };
|
||||||
|
const splitterContentPanelStyle = { padding: 16 };
|
||||||
|
const splitterSiderPanelStyle = { padding: "16px", borderLeft: "1px solid #ddd", overflowY: "auto" };
|
||||||
|
const siderTitleStyle = { marginBottom: 36 };
|
||||||
|
const siderButtonContainerStyle = {
|
||||||
|
position: "fixed",
|
||||||
|
right: 0,
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
transition: "right 0.3s ease",
|
||||||
|
zIndex: 1000,
|
||||||
|
display: screens.xs ? "none" : "block",
|
||||||
|
};
|
||||||
|
const siderButtonStyle = {
|
||||||
|
width: hovered ? 250 : 50,
|
||||||
|
padding: hovered ? "0 20px" : "0",
|
||||||
|
overflow: "hidden",
|
||||||
|
textAlign: "left",
|
||||||
|
transition: "width 0.3s ease, padding 0.3s ease",
|
||||||
|
borderRadius: "4px 0 0 4px",
|
||||||
|
};
|
||||||
|
const badgeTextStyle = {
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
display: "inline-block",
|
||||||
|
width: "100%",
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleSider = () => dispatch(toggleSider());
|
||||||
|
const handleHoverSider = () => dispatch(setHovered(true));
|
||||||
|
const handleLeaveSider = () => dispatch(setHovered(false));
|
||||||
|
const handleSetSiderWidth = (width) => setLocalSiderWidth(width);
|
||||||
|
|
||||||
|
const handleCancelViewModal = () => {
|
||||||
|
if (selectedAppointment) {
|
||||||
|
dispatch(setSelectedAppointment(null));
|
||||||
|
} else {
|
||||||
|
dispatch(setSelectedScheduledAppointment(null));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEventClick = (event) => {
|
||||||
|
if (event.appointment_datetime) {
|
||||||
|
dispatch(setSelectedAppointment(event));
|
||||||
|
} else {
|
||||||
|
dispatch(setSelectedScheduledAppointment(event));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateScheduledAppointmentModal = () => {
|
||||||
|
dispatch(openScheduledModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateAppointmentModal = () => dispatch(openModal());
|
||||||
|
|
||||||
|
const siderButtonText = useMemo(() =>
|
||||||
|
hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "",
|
||||||
|
[collapsed, hovered]
|
||||||
|
);
|
||||||
|
const showSplitterPanel = useMemo(() => !collapsed && !screens.xs, [collapsed, screens]);
|
||||||
|
|
||||||
|
const upcomingEvents = useMemo(() =>
|
||||||
|
[...upcomingAppointments, ...upcomingScheduledAppointments]
|
||||||
|
.sort((a, b) => dayjs(a.appointment_datetime || a.scheduled_datetime) - dayjs(b.appointment_datetime || b.scheduled_datetime))
|
||||||
|
.slice(0, 5),
|
||||||
|
[upcomingAppointments, upcomingScheduledAppointments]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "Приемы";
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isErrorAppointments) {
|
if (isErrorAppointments) {
|
||||||
notification.error({
|
notification.error({
|
||||||
@ -56,14 +185,60 @@ const useAppointments = () => {
|
|||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isErrorAppointments, isErrorScheduledAppointments, isErrorPatients]);
|
if (isErrorUpcomingAppointments) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Ошибка',
|
||||||
|
description: 'Ошибка загрузки предстоящих приемов.',
|
||||||
|
placement: 'topRight',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isErrorUpcomingScheduledAppointments) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Ошибка',
|
||||||
|
description: 'Ошибка загрузки предстоящих запланированных приемов.',
|
||||||
|
placement: 'topRight',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isErrorAppointments,
|
||||||
|
isErrorScheduledAppointments,
|
||||||
|
isErrorPatients,
|
||||||
|
isErrorUpcomingAppointments,
|
||||||
|
isErrorUpcomingScheduledAppointments
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
patients,
|
patients,
|
||||||
appointments,
|
appointments,
|
||||||
scheduledAppointments,
|
scheduledAppointments,
|
||||||
isLoading: isLoadingAppointments || isLoadingScheduledAppointments || isLoadingPatients,
|
isLoading: isLoadingAppointments || isLoadingScheduledAppointments || isLoadingPatients ||
|
||||||
isError: isErrorAppointments || isErrorScheduledAppointments || isErrorPatients,
|
isLoadingUpcomingAppointments || isLoadingUpcomingScheduledAppointments,
|
||||||
|
isError: isErrorAppointments || isErrorScheduledAppointments || isErrorPatients ||
|
||||||
|
isErrorUpcomingAppointments || isErrorUpcomingScheduledAppointments,
|
||||||
|
collapsed,
|
||||||
|
siderWidth: localSiderWidth,
|
||||||
|
hovered,
|
||||||
|
showSplitterPanel,
|
||||||
|
siderButtonText,
|
||||||
|
splitterStyle,
|
||||||
|
splitterContentPanelStyle,
|
||||||
|
splitterSiderPanelStyle,
|
||||||
|
siderTitleStyle,
|
||||||
|
siderButtonContainerStyle,
|
||||||
|
siderButtonStyle,
|
||||||
|
badgeTextStyle,
|
||||||
|
upcomingEvents,
|
||||||
|
selectedAppointment,
|
||||||
|
handleCancelViewModal,
|
||||||
|
handleToggleSider,
|
||||||
|
handleHoverSider,
|
||||||
|
handleLeaveSider,
|
||||||
|
handleSetSiderWidth,
|
||||||
|
openCreateScheduledAppointmentModal,
|
||||||
|
currentMonth,
|
||||||
|
handleMonthChange,
|
||||||
|
handleEventClick,
|
||||||
|
openCreateAppointmentModal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { Grid } from "antd";
|
|
||||||
import {
|
|
||||||
setHovered,
|
|
||||||
setSelectedAppointment,
|
|
||||||
setSelectedScheduledAppointment,
|
|
||||||
toggleSider,
|
|
||||||
openScheduledModal,
|
|
||||||
} from "../../../Redux/Slices/appointmentsSlice.js";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
|
||||||
|
|
||||||
const useAppointmentsUI = (appointments, scheduledAppointments) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const {
|
|
||||||
collapsed,
|
|
||||||
siderWidth,
|
|
||||||
hovered,
|
|
||||||
selectedAppointment,
|
|
||||||
} = useSelector(state => state.appointmentsUI);
|
|
||||||
const screens = useBreakpoint();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = "Приемы";
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const splitterStyle = { flex: 1 };
|
|
||||||
const splitterContentPanelStyle = { padding: 16 };
|
|
||||||
const splitterSiderPanelStyle = { padding: "16px", borderLeft: "1px solid #ddd", overflowY: "auto" };
|
|
||||||
const siderTitleStyle = { marginBottom: 36 };
|
|
||||||
const siderButtonContainerStyle = {
|
|
||||||
position: "fixed",
|
|
||||||
right: 0,
|
|
||||||
top: "50%",
|
|
||||||
transform: "translateY(-50%)",
|
|
||||||
transition: "right 0.3s ease",
|
|
||||||
zIndex: 1000,
|
|
||||||
display: screens.xs ? "none" : "block",
|
|
||||||
};
|
|
||||||
const siderButtonStyle = {
|
|
||||||
width: hovered ? 250 : 50,
|
|
||||||
padding: hovered ? "0 20px" : "0",
|
|
||||||
overflow: "hidden",
|
|
||||||
textAlign: "left",
|
|
||||||
transition: "width 0.3s ease, padding 0.3s ease",
|
|
||||||
borderRadius: "4px 0 0 4px",
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleToggleSider = () => dispatch(toggleSider());
|
|
||||||
const handleHoverSider = () => dispatch(setHovered(true));
|
|
||||||
const handleLeaveSider = () => dispatch(setHovered(false));
|
|
||||||
|
|
||||||
const handleCancelViewModal = () => {
|
|
||||||
if (selectedAppointment) {
|
|
||||||
dispatch(setSelectedAppointment(null));
|
|
||||||
} else {
|
|
||||||
dispatch(setSelectedScheduledAppointment(null));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openCreateScheduledAppointmentModal = () => {
|
|
||||||
dispatch(openScheduledModal());
|
|
||||||
};
|
|
||||||
|
|
||||||
const siderButtonText = useMemo(() => hovered ? (collapsed ? "Показать предстоящие события" : "Скрыть предстоящие события") : "", [collapsed, hovered]);
|
|
||||||
const showSplitterPanel = useMemo(() => !collapsed && !screens.xs, [collapsed, screens]);
|
|
||||||
|
|
||||||
const upcomingEvents = [...appointments, ...scheduledAppointments]
|
|
||||||
.filter(app => dayjs(app.appointment_datetime || app.scheduled_datetime).isAfter(dayjs()))
|
|
||||||
.sort((a, b) => dayjs(a.appointment_datetime || a.scheduled_datetime) - dayjs(b.appointment_datetime || b.scheduled_datetime))
|
|
||||||
.slice(0, 5);
|
|
||||||
|
|
||||||
return {
|
|
||||||
collapsed,
|
|
||||||
siderWidth,
|
|
||||||
hovered,
|
|
||||||
showSplitterPanel,
|
|
||||||
siderButtonText,
|
|
||||||
splitterStyle,
|
|
||||||
splitterContentPanelStyle,
|
|
||||||
splitterSiderPanelStyle,
|
|
||||||
siderTitleStyle,
|
|
||||||
siderButtonContainerStyle,
|
|
||||||
siderButtonStyle,
|
|
||||||
upcomingEvents,
|
|
||||||
selectedAppointment,
|
|
||||||
handleCancelViewModal,
|
|
||||||
handleToggleSider,
|
|
||||||
handleHoverSider,
|
|
||||||
handleLeaveSider,
|
|
||||||
openCreateScheduledAppointmentModal,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useAppointmentsUI;
|
|
||||||
@ -31,7 +31,13 @@ ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend)
|
|||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const homePageData = useHomePage();
|
const homePageData = useHomePage();
|
||||||
const homePageUI = useHomePageUI(homePageData.appointments, homePageData.scheduledAppointments, homePageData.patients);
|
const homePageUI = useHomePageUI(
|
||||||
|
homePageData.appointments,
|
||||||
|
homePageData.scheduledAppointments,
|
||||||
|
homePageData.patients,
|
||||||
|
homePageData.upcomingAppointments,
|
||||||
|
homePageData.upcomingScheduledAppointments
|
||||||
|
);
|
||||||
|
|
||||||
if (homePageData.isError) {
|
if (homePageData.isError) {
|
||||||
return (
|
return (
|
||||||
@ -94,7 +100,7 @@ const HomePage = () => {
|
|||||||
<Statistic
|
<Statistic
|
||||||
title="Приемы за месяц"
|
title="Приемы за месяц"
|
||||||
value={
|
value={
|
||||||
homePageData.appointments.filter((a) => dayjs(a.appointment_datetime).isSame(dayjs(), "month")).length
|
homePageData.appointments.length
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@ -107,7 +113,7 @@ const HomePage = () => {
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
title={`События на сегодня (${dayjs().format("DD.MM.YYYY")})`}
|
title={`События на сегодня (${dayjs().tz("Europe/Moscow").format("DD.MM.YYYY")})`}
|
||||||
style={homePageUI.sectionStyle}
|
style={homePageUI.sectionStyle}
|
||||||
>
|
>
|
||||||
<List
|
<List
|
||||||
@ -119,7 +125,7 @@ const HomePage = () => {
|
|||||||
>
|
>
|
||||||
<Space>
|
<Space>
|
||||||
<Typography.Text strong>
|
<Typography.Text strong>
|
||||||
{dayjs(item.appointment_datetime || item.scheduled_datetime).format("HH:mm")}
|
{dayjs(item.appointment_datetime || item.scheduled_datetime).tz("Europe/Moscow").format("HH:mm")}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text>
|
<Typography.Text>
|
||||||
{item.patient ? `${item.patient.last_name} ${item.patient.first_name}` : "Без пациента"} -{" "}
|
{item.patient ? `${item.patient.last_name} ${item.patient.first_name}` : "Без пациента"} -{" "}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import {useGetAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
import {useGetAppointmentsQuery, useGetUpcomingAppointmentsQuery} from "../../../Api/appointmentsApi.js";
|
||||||
import {useGetScheduledAppointmentsQuery} from "../../../Api/scheduledAppointmentsApi.js";
|
import {
|
||||||
import {useGetPatientsQuery} from "../../../Api/patientsApi.js";
|
useGetScheduledAppointmentsQuery,
|
||||||
|
useGetUpcomingScheduledAppointmentsQuery
|
||||||
|
} from "../../../Api/scheduledAppointmentsApi.js";
|
||||||
|
import {useGetAllPatientsQuery} from "../../../Api/patientsApi.js";
|
||||||
import {notification} from "antd";
|
import {notification} from "antd";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
@ -14,40 +17,65 @@ import {
|
|||||||
openModal as openPatientModal,
|
openModal as openPatientModal,
|
||||||
} from "../../../Redux/Slices/patientsSlice.js";
|
} from "../../../Redux/Slices/patientsSlice.js";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import isBetween from "dayjs/plugin/isBetween";
|
import isBetween from "dayjs/plugin/isBetween";
|
||||||
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js"; // Import isBetween plugin
|
import {useGetAppointmentTypesQuery} from "../../../Api/appointmentTypesApi.js";
|
||||||
|
|
||||||
dayjs.extend(isBetween); // Extend dayjs with isBetween
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
dayjs.extend(isBetween);
|
||||||
|
|
||||||
const useHomePage = () => {
|
const useHomePage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const {userData} = useSelector((state) => state.auth);
|
||||||
|
|
||||||
const {
|
const startDate = dayjs().startOf('month').format('YYYY-MM-DD');
|
||||||
userData
|
const endDate = dayjs().endOf('month').format('YYYY-MM-DD');
|
||||||
} = useSelector((state) => state.auth);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: appointments = [],
|
data: appointments = [],
|
||||||
isLoading: isLoadingAppointments,
|
isLoading: isLoadingAppointments,
|
||||||
isError: isErrorAppointments,
|
isError: isErrorAppointments,
|
||||||
} = useGetAppointmentsQuery((userData.id), {
|
} = useGetAppointmentsQuery({doctor_id: userData.id, start_date: startDate, end_date: endDate}, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: scheduledAppointments = [],
|
data: scheduledAppointments = [],
|
||||||
isLoading: isLoadingScheduledAppointments,
|
isLoading: isLoadingScheduledAppointments,
|
||||||
isError: isErrorScheduledAppointments,
|
isError: isErrorScheduledAppointments,
|
||||||
} = useGetScheduledAppointmentsQuery((userData.id), {
|
} = useGetScheduledAppointmentsQuery({doctor_id: userData.id, start_date: startDate, end_date: endDate}, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: upcomingAppointments = [],
|
||||||
|
isLoading: isLoadingUpcomingAppointments,
|
||||||
|
isError: isErrorUpcomingAppointments,
|
||||||
|
} = useGetUpcomingAppointmentsQuery(userData.id, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: upcomingScheduledAppointments = [],
|
||||||
|
isLoading: isLoadingUpcomingScheduledAppointments,
|
||||||
|
isError: isErrorUpcomingScheduledAppointments,
|
||||||
|
} = useGetUpcomingScheduledAppointmentsQuery(userData.id, {
|
||||||
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: patients = [],
|
data: patients = [],
|
||||||
isLoading: isLoadingPatients,
|
isLoading: isLoadingPatients,
|
||||||
isError: isErrorPatients,
|
isError: isErrorPatients,
|
||||||
} = useGetPatientsQuery(undefined, {
|
} = useGetAllPatientsQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -55,7 +83,8 @@ const useHomePage = () => {
|
|||||||
isLoading: isLoadingAppointmentTypes,
|
isLoading: isLoadingAppointmentTypes,
|
||||||
isError: isErrorAppointmentTypes,
|
isError: isErrorAppointmentTypes,
|
||||||
} = useGetAppointmentTypesQuery(undefined, {
|
} = useGetAppointmentTypesQuery(undefined, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 60000,
|
||||||
|
skip: !userData.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -73,6 +102,20 @@ const useHomePage = () => {
|
|||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (isErrorUpcomingAppointments) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: "Ошибка загрузки предстоящих приемов.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isErrorUpcomingScheduledAppointments) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка",
|
||||||
|
description: "Ошибка загрузки предстоящих запланированных приемов.",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
if (isErrorPatients) {
|
if (isErrorPatients) {
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка",
|
message: "Ошибка",
|
||||||
@ -87,7 +130,14 @@ const useHomePage = () => {
|
|||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [isErrorAppointments, isErrorScheduledAppointments, isErrorPatients, isErrorAppointmentTypes]);
|
}, [
|
||||||
|
isErrorAppointments,
|
||||||
|
isErrorScheduledAppointments,
|
||||||
|
isErrorUpcomingAppointments,
|
||||||
|
isErrorUpcomingScheduledAppointments,
|
||||||
|
isErrorPatients,
|
||||||
|
isErrorAppointmentTypes
|
||||||
|
]);
|
||||||
|
|
||||||
const handleEventClick = (event) => {
|
const handleEventClick = (event) => {
|
||||||
if (event.appointment_datetime) {
|
if (event.appointment_datetime) {
|
||||||
@ -113,15 +163,21 @@ const useHomePage = () => {
|
|||||||
patients,
|
patients,
|
||||||
appointments,
|
appointments,
|
||||||
scheduledAppointments,
|
scheduledAppointments,
|
||||||
|
upcomingAppointments,
|
||||||
|
upcomingScheduledAppointments,
|
||||||
appointmentTypes,
|
appointmentTypes,
|
||||||
isLoading:
|
isLoading:
|
||||||
isLoadingAppointments ||
|
isLoadingAppointments ||
|
||||||
isLoadingScheduledAppointments ||
|
isLoadingScheduledAppointments ||
|
||||||
|
isLoadingUpcomingAppointments ||
|
||||||
|
isLoadingUpcomingScheduledAppointments ||
|
||||||
isLoadingPatients ||
|
isLoadingPatients ||
|
||||||
isLoadingAppointmentTypes,
|
isLoadingAppointmentTypes,
|
||||||
isError:
|
isError:
|
||||||
isErrorAppointments ||
|
isErrorAppointments ||
|
||||||
isErrorScheduledAppointments ||
|
isErrorScheduledAppointments ||
|
||||||
|
isErrorUpcomingAppointments ||
|
||||||
|
isErrorUpcomingScheduledAppointments ||
|
||||||
isErrorPatients ||
|
isErrorPatients ||
|
||||||
isErrorAppointmentTypes,
|
isErrorAppointmentTypes,
|
||||||
handleEventClick,
|
handleEventClick,
|
||||||
|
|||||||
@ -1,32 +1,35 @@
|
|||||||
import { Grid } from "antd";
|
import {Grid} from "antd";
|
||||||
import {useEffect, useMemo} from "react";
|
import {useEffect, useMemo} from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import isBetween from "dayjs/plugin/isBetween";
|
import isBetween from "dayjs/plugin/isBetween";
|
||||||
import {useSelector} from "react-redux"; // Import isBetween plugin
|
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
dayjs.extend(isBetween);
|
dayjs.extend(isBetween);
|
||||||
|
|
||||||
const { useBreakpoint } = Grid;
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
const useHomePageUI = (appointments, scheduledAppointments, patients, upcomingAppointments, upcomingScheduledAppointments) => {
|
||||||
const screens = useBreakpoint();
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
const containerStyle = { padding: screens.xs ? 16 : 24 };
|
const containerStyle = {padding: screens.xs ? 16 : 24};
|
||||||
const sectionStyle = { marginBottom: 24 };
|
const sectionStyle = {marginBottom: 24};
|
||||||
const cardStyle = { height: "100%" };
|
const cardStyle = {height: "100%"};
|
||||||
const listItemStyle = { cursor: "pointer", padding: "12px", borderRadius: 4 };
|
const listItemStyle = {cursor: "pointer", padding: "12px", borderRadius: 4};
|
||||||
const buttonStyle = { width: screens.xs ? "100%" : "auto" };
|
const buttonStyle = {width: screens.xs ? "100%" : "auto"};
|
||||||
const chartContainerStyle = { padding: 16, background: "#fff", borderRadius: 4 };
|
const chartContainerStyle = {padding: 16, background: "#fff", borderRadius: 4};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Главная страница";
|
document.title = "Главная страница";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const todayEvents = useMemo(() => {
|
const todayEvents = useMemo(() => {
|
||||||
return [...appointments, ...scheduledAppointments].filter((event) =>
|
return [...upcomingAppointments, ...upcomingScheduledAppointments].filter((event) =>
|
||||||
dayjs(event.appointment_datetime || event.scheduled_datetime).isSame(dayjs(), "day")
|
dayjs(event.appointment_datetime || event.scheduled_datetime).isSame(dayjs(), "day")
|
||||||
);
|
);
|
||||||
}, [appointments, scheduledAppointments]);
|
}, [upcomingAppointments, upcomingScheduledAppointments]);
|
||||||
|
|
||||||
const upcomingBirthdays = useMemo(() => {
|
const upcomingBirthdays = useMemo(() => {
|
||||||
return patients.filter((p) =>
|
return patients.filter((p) =>
|
||||||
@ -38,7 +41,12 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
|||||||
const data = Array(7).fill(0);
|
const data = Array(7).fill(0);
|
||||||
appointments
|
appointments
|
||||||
.filter((app) =>
|
.filter((app) =>
|
||||||
dayjs(app.appointment_datetime).isBetween(dayjs().startOf("week"), dayjs().endOf("week"), "day", "[]")
|
dayjs(app.appointment_datetime).isBetween(
|
||||||
|
dayjs().startOf("week"),
|
||||||
|
dayjs().endOf("week"),
|
||||||
|
"day",
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.forEach((app) => {
|
.forEach((app) => {
|
||||||
const dayIndex = dayjs(app.appointment_datetime).day();
|
const dayIndex = dayjs(app.appointment_datetime).day();
|
||||||
@ -51,7 +59,12 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
|||||||
const data = Array(7).fill(0);
|
const data = Array(7).fill(0);
|
||||||
scheduledAppointments
|
scheduledAppointments
|
||||||
.filter((app) =>
|
.filter((app) =>
|
||||||
dayjs(app.scheduled_datetime).isBetween(dayjs().startOf("week"), dayjs().endOf("week"), "day", "[]")
|
dayjs(app.scheduled_datetime).isBetween(
|
||||||
|
dayjs().startOf("week"),
|
||||||
|
dayjs().endOf("week"),
|
||||||
|
"day",
|
||||||
|
"[]"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.forEach((app) => {
|
.forEach((app) => {
|
||||||
const dayIndex = dayjs(app.scheduled_datetime).day();
|
const dayIndex = dayjs(app.scheduled_datetime).day();
|
||||||
@ -64,12 +77,12 @@ const useHomePageUI = (appointments, scheduledAppointments, patients) => {
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
scales: {
|
scales: {
|
||||||
y: { beginAtZero: true, title: { display: true, text: "Количество приемов" } },
|
y: {beginAtZero: true, title: {display: true, text: "Количество приемов"}},
|
||||||
x: { title: { display: true, text: "День недели" } },
|
x: {title: {display: true, text: "День недели"}},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: {display: true, position: "top"},
|
||||||
title: { display: true, text: "Приемы за неделю" },
|
title: {display: true, text: "Приемы за неделю"},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import {useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
|
import {useGetAllPatientsQuery, useGetPatientsQuery} from "../../../../../Api/patientsApi.js";
|
||||||
import {useGetNotIssuedLensesQuery} from "../../../../../Api/lensesApi.js";
|
import {useGetNotIssuedLensesQuery} from "../../../../../Api/lensesApi.js";
|
||||||
|
|
||||||
|
|
||||||
const useLensIssueForm = () => {
|
const useLensIssueForm = () => {
|
||||||
const {data: patients = [], isLoading: isLoadingPatients, isError: isErrorPatients} = useGetPatientsQuery(undefined, {
|
const {data: patients = [], isLoading: isLoadingPatients, isError: isErrorPatients} = useGetAllPatientsQuery(undefined, {
|
||||||
pollingInterval: 10000,
|
pollingInterval: 10000,
|
||||||
});
|
});
|
||||||
const {data: lenses = [], isLoading: isLoadingLenses, isError: isErrorLenses} = useGetNotIssuedLensesQuery(undefined, {
|
const {data: lenses = [], isLoading: isLoadingLenses, isError: isErrorLenses} = useGetNotIssuedLensesQuery(undefined, {
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Timeline,
|
Timeline,
|
||||||
Grid,
|
Grid,
|
||||||
Pagination, Result
|
Pagination,
|
||||||
|
Result
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import {DatabaseOutlined, PlusOutlined, UnorderedListOutlined} from "@ant-design/icons";
|
import {DatabaseOutlined, PlusOutlined, UnorderedListOutlined} from "@ant-design/icons";
|
||||||
import LensIssueViewModal from "./Components/LensIssueViewModal/LensIssueViewModal.jsx";
|
import LensIssueViewModal from "./Components/LensIssueViewModal/LensIssueViewModal.jsx";
|
||||||
@ -19,27 +20,25 @@ import LensIssueFormModal from "./Components/LensIssueFormModal/LensIssueFormMod
|
|||||||
import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx";
|
import SelectViewMode from "../../Widgets/SelectViewMode/SelectViewMode.jsx";
|
||||||
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
import LoadingIndicator from "../../Widgets/LoadingIndicator/LoadingIndicator.jsx";
|
||||||
import useIssues from "./useIssues.js";
|
import useIssues from "./useIssues.js";
|
||||||
import useIssuesUI from "./useIssuesUI.js";
|
import {useMemo} from "react";
|
||||||
|
|
||||||
const {Title} = Typography;
|
const {Title} = Typography;
|
||||||
const {useBreakpoint} = Grid;
|
const {useBreakpoint} = Grid;
|
||||||
|
|
||||||
const IssuesPage = () => {
|
const IssuesPage = () => {
|
||||||
const issuesData = useIssues();
|
const issuesData = useIssues();
|
||||||
const issuesUI = useIssuesUI(issuesData.issues);
|
|
||||||
|
|
||||||
const screens = useBreakpoint();
|
const screens = useBreakpoint();
|
||||||
|
|
||||||
const viewModes = [
|
const viewModes = [
|
||||||
{
|
{
|
||||||
value: "table",
|
value: "table",
|
||||||
label: "Таблица",
|
label: "Таблица",
|
||||||
icon: <DatabaseOutlined style={issuesUI.viewModIconStyle}/>,
|
icon: <DatabaseOutlined style={issuesData.viewModIconStyle}/>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: "timeline",
|
value: "timeline",
|
||||||
label: "Лента",
|
label: "Лента",
|
||||||
icon: <UnorderedListOutlined style={issuesUI.viewModIconStyle}/>,
|
icon: <UnorderedListOutlined style={issuesData.viewModIconStyle}/>,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ const IssuesPage = () => {
|
|||||||
title: "Действия",
|
title: "Действия",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (_, issue) => (
|
render: (_, issue) => (
|
||||||
<Button type={"link"} onClick={() => issuesUI.handleSelectIssue(issue)}>Подробнее</Button>
|
<Button type="link" onClick={() => issuesData.handleSelectIssue(issue)}>Подробнее</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -81,30 +80,28 @@ const IssuesPage = () => {
|
|||||||
const TableView = () => (
|
const TableView = () => (
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={issuesUI.filteredIssues}
|
dataSource={issuesData.issues}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
pagination={issuesUI.pagination}
|
pagination={issuesData.pagination}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const timeLineItems = issuesUI.filteredIssues.map(issue => ({
|
const TimeLineView = () => {
|
||||||
|
const timeLineItems = useMemo(() => issuesData.issues.map(issue => ({
|
||||||
label: dayjs(issue.issue_date).format("DD.MM.YYYY"),
|
label: dayjs(issue.issue_date).format("DD.MM.YYYY"),
|
||||||
children: (
|
children: (
|
||||||
<Row
|
<Row gutter={[16, 16]} align="middle">
|
||||||
gutter={[16, 16]}
|
<Col xs={24} sm={24} md={13}>
|
||||||
align={"middle"}
|
|
||||||
>
|
|
||||||
<Col xs={24} md={24} sm={24} xl={13}>
|
|
||||||
<p style={{textAlign: "right"}}>Пациент: {issue.patient.last_name} {issue.patient.first_name}</p>
|
<p style={{textAlign: "right"}}>Пациент: {issue.patient.last_name} {issue.patient.first_name}</p>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={24} sm={24} xl={5}>
|
<Col xs={24} sm={24} md={5}>
|
||||||
<p style={{textAlign: "right"}}>Линза: {issue.lens.side} {issue.lens.diameter}</p>
|
<p style={{textAlign: "right"}}>Линза: {issue.lens.side} {issue.lens.diameter}</p>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} md={24} sm={24} xl={6}>
|
<Col xs={24} sm={24} md={6}>
|
||||||
<Button
|
<Button
|
||||||
type={"dashed"}
|
type="dashed"
|
||||||
onClick={() => issuesUI.handleSelectIssue(issue)}
|
onClick={() => issuesData.handleSelectIssue(issue)}
|
||||||
style={{marginRight: 40}}
|
style={{marginRight: 40}}
|
||||||
>
|
>
|
||||||
Подробнее
|
Подробнее
|
||||||
@ -112,32 +109,21 @@ const IssuesPage = () => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
),
|
),
|
||||||
}));
|
})), []);
|
||||||
|
|
||||||
const TimeLineView = () => {
|
|
||||||
const paginatedItems = timeLineItems.slice(
|
|
||||||
(issuesUI.currentPage - 1) * issuesUI.pageSize,
|
|
||||||
issuesUI.currentPage * issuesUI.pageSize
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Timeline
|
<Timeline
|
||||||
items={paginatedItems}
|
items={timeLineItems}
|
||||||
mode={screens.xs ? "left" : "right"}
|
mode={screens.xs ? "left" : "right"}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
style={{textAlign: "center", marginTop: 20}}
|
style={{textAlign: "center", marginTop: 20}}
|
||||||
align={"middle"}
|
align="middle"
|
||||||
justify={"end"}
|
justify="end"
|
||||||
>
|
>
|
||||||
<Pagination
|
<Pagination
|
||||||
current={issuesUI.currentPage}
|
{...issuesData.pagination}
|
||||||
pageSize={issuesUI.pageSize}
|
|
||||||
total={timeLineItems.length}
|
|
||||||
onChange={issuesUI.handlePaginationChange}
|
|
||||||
showSizeChanger={true}
|
|
||||||
pageSizeOptions={["5", "10", "20", "50"]}
|
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
@ -153,46 +139,37 @@ const IssuesPage = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={issuesUI.containerStyle}>
|
<div style={issuesData.containerStyle}>
|
||||||
<Title level={1}><DatabaseOutlined/> Выдача линз</Title>
|
<Title level={1}><DatabaseOutlined/> Выдача линз</Title>
|
||||||
<Row gutter={[16, 16]} style={issuesUI.filterBarStyle}>
|
<Row gutter={[16, 16]} style={issuesData.filterBarStyle}>
|
||||||
<Col xs={24} md={24} sm={24} xl={12}>
|
<Col xs={24} sm={24} md={12}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Поиск по пациенту или врачу"
|
placeholder="Поиск по пациенту или врачу"
|
||||||
onChange={(e) => issuesUI.handleSetSearchText(e.target.value)}
|
value={issuesData.tempSearchText}
|
||||||
style={issuesUI.formItemStyle}
|
onChange={(e) => issuesData.handleSetTempSearchText(e.target.value)}
|
||||||
|
style={issuesData.formItemStyle}
|
||||||
allowClear
|
allowClear
|
||||||
|
onClear={issuesData.handleClearSearch}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col xs={24} sm={24} md={issuesData.isFilterDates ? 6 : 8}>
|
||||||
<Col xs={24} md={
|
<Tooltip title="Фильтр по дате выдачи линзы">
|
||||||
issuesUI.isFilterDates ? 12 : 16
|
|
||||||
} sm={
|
|
||||||
16
|
|
||||||
} xl={
|
|
||||||
issuesUI.isFilterDates ? 6 : 8
|
|
||||||
}>
|
|
||||||
<Tooltip
|
|
||||||
title="Фильтр по дате выдачи линзы"
|
|
||||||
>
|
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
allowClear={false}
|
allowClear={false}
|
||||||
style={issuesUI.formItemStyle}
|
style={issuesData.formItemStyle}
|
||||||
placeholder={["Дата начала", "Дата окончания"]}
|
placeholder={["Дата начала", "Дата окончания"]}
|
||||||
format="DD.MM.YYYY"
|
format="DD.MM.YYYY"
|
||||||
value={issuesUI.isFilterDates && issuesUI.filterDates}
|
value={issuesData.isFilterDates && issuesData.filterDates}
|
||||||
onChange={issuesUI.handleFilterDateChange}
|
onChange={issuesData.handleFilterDateChange}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
{issuesUI.isFilterDates && (
|
{issuesData.isFilterDates && (
|
||||||
<Col xs={24} md={4} sm={8} xl={2}>
|
<Col xs={24} sm={24} md={2}>
|
||||||
<Tooltip
|
<Tooltip title="Cбросить фильтр">
|
||||||
title="Cбросить фильтр"
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={issuesUI.handleResetFilterDate}
|
onClick={issuesData.handleResetFilterDate}
|
||||||
type={"primary"}
|
type="primary"
|
||||||
block
|
block
|
||||||
>
|
>
|
||||||
Сбросить
|
Сбросить
|
||||||
@ -200,22 +177,19 @@ const IssuesPage = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
<Col xs={24}
|
<Col xs={24} sm={24} md={issuesData.isFilterDates ? 4 : 4}>
|
||||||
md={issuesUI.isFilterDates ? 8 : 8}
|
|
||||||
sm={issuesUI.isFilterDates ? 24 : 8}
|
|
||||||
xl={4}>
|
|
||||||
<SelectViewMode
|
<SelectViewMode
|
||||||
viewMode={issuesUI.viewMode}
|
viewMode={issuesData.viewMode}
|
||||||
setViewMode={issuesUI.handleSetViewMode}
|
setViewMode={issuesData.handleSetViewMode}
|
||||||
localStorageKey={"viewModeIssues"}
|
localStorageKey="viewModeIssues"
|
||||||
toolTipText={"Формат отображения выдач линз"}
|
toolTipText="Формат отображения выдач линз"
|
||||||
viewModes={viewModes}
|
viewModes={viewModes}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{issuesData.isLoading ? (
|
{issuesData.isLoading ? (
|
||||||
<LoadingIndicator/>
|
<LoadingIndicator/>
|
||||||
) : issuesUI.viewMode === "table" ? (
|
) : issuesData.viewMode === "table" ? (
|
||||||
<TableView/>
|
<TableView/>
|
||||||
) : (
|
) : (
|
||||||
<TimeLineView/>
|
<TimeLineView/>
|
||||||
@ -223,21 +197,24 @@ const IssuesPage = () => {
|
|||||||
|
|
||||||
<FloatButton
|
<FloatButton
|
||||||
icon={<PlusOutlined/>}
|
icon={<PlusOutlined/>}
|
||||||
type={"primary"}
|
type="primary"
|
||||||
onClick={issuesUI.handleAddIssue}
|
onClick={issuesData.handleAddIssue}
|
||||||
tooltip={"Добавить выдачу линзы"}
|
tooltip="Добавить выдачу линзы"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LensIssueFormModal
|
<LensIssueFormModal
|
||||||
visible={issuesUI.isModalVisible}
|
visible={issuesData.isModalVisible}
|
||||||
onCancel={issuesUI.handleCloseModal}
|
onCancel={issuesData.handleCloseModal}
|
||||||
onSubmit={issuesData.handleSubmitFormModal}
|
onSubmit={issuesData.handleSubmitFormModal}
|
||||||
|
isProcessing={issuesData.isProcessing}
|
||||||
|
patients={issuesData.patients}
|
||||||
|
lenses={issuesData.lenses}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LensIssueViewModal
|
<LensIssueViewModal
|
||||||
visible={issuesUI.selectedIssue !== null}
|
visible={issuesData.selectedIssue !== null}
|
||||||
onCancel={issuesUI.resetSelectedIssue}
|
onCancel={issuesData.resetSelectedIssue}
|
||||||
lensIssue={issuesUI.selectedIssue}
|
lensIssue={issuesData.selectedIssue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,28 +1,183 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import {useDispatch, useSelector} from "react-redux";
|
||||||
import {useAddLensIssuesMutation, useGetLensIssuesQuery} from "../../../Api/lensIssuesApi.js";
|
|
||||||
import {notification} from "antd";
|
import {notification} from "antd";
|
||||||
import {closeModal} from "../../../Redux/Slices/lensIssuesSlice.js";
|
import {
|
||||||
|
useAddLensIssuesMutation,
|
||||||
|
useGetLensIssuesQuery,
|
||||||
|
} from "../../../Api/lensIssuesApi.js";
|
||||||
|
import {useGetAllPatientsQuery} from "../../../Api/patientsApi.js";
|
||||||
|
import {
|
||||||
|
closeModal,
|
||||||
|
openModal,
|
||||||
|
selectIssue,
|
||||||
|
setCurrentPage,
|
||||||
|
setEndFilterDate,
|
||||||
|
setPageSize,
|
||||||
|
setViewMode,
|
||||||
|
setStartFilterDate,
|
||||||
|
} from "../../../Redux/Slices/lensIssuesSlice.js";
|
||||||
|
import {getCachedInfo} from "../../../Utils/cachedInfoUtils.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {useGetNotIssuedLensesQuery} from "../../../Api/lensesApi.js";
|
||||||
|
|
||||||
const useIssues = () => {
|
const useIssues = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
selectedIssue,
|
||||||
|
isModalVisible,
|
||||||
|
viewMode,
|
||||||
|
startFilterDate,
|
||||||
|
endFilterDate,
|
||||||
|
} = useSelector(state => state.lensIssuesUI);
|
||||||
|
|
||||||
const {data: issues = [], isLoading, isError, error} = useGetLensIssuesQuery(undefined, {
|
const [tempSearchText, setTempSearchText] = useState("");
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: issuesData = {issues: [], total_count: 0},
|
||||||
|
isLoading: isIssuesLoading,
|
||||||
|
isError: isIssuesError,
|
||||||
|
error: issuesError,
|
||||||
|
refetch
|
||||||
|
} = useGetLensIssuesQuery({
|
||||||
|
page: currentPage,
|
||||||
|
pageSize,
|
||||||
|
search: tempSearchText || undefined, // Используем tempSearchText напрямую
|
||||||
|
sortOrder: 'desc',
|
||||||
|
startDate: startFilterDate ? dayjs(startFilterDate).format('YYYY-MM-DD') : undefined,
|
||||||
|
endDate: endFilterDate ? dayjs(endFilterDate).format('YYYY-MM-DD') : undefined,
|
||||||
|
}, {
|
||||||
pollingInterval: 20000,
|
pollingInterval: 20000,
|
||||||
});
|
});
|
||||||
const [addIssue] = useAddLensIssuesMutation();
|
|
||||||
|
const {data: patients = [], isLoading: isPatientsLoading, isError: isPatientsError} = useGetAllPatientsQuery();
|
||||||
|
const {data: lenses = [], isLoading: isLensesLoading, isError: isLensesError} = useGetNotIssuedLensesQuery();
|
||||||
|
const [addIssue, {isLoading: isAdding}] = useAddLensIssuesMutation();
|
||||||
|
|
||||||
|
const isLoading = isIssuesLoading || isPatientsLoading || isLensesLoading;
|
||||||
|
const isError = isIssuesError || isPatientsError || isLensesError;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = "Выдача линз";
|
||||||
|
const cachedViewMode = getCachedInfo("viewModeIssues");
|
||||||
|
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isIssuesError) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка загрузки выдач",
|
||||||
|
description: issuesError?.data?.detail || "Не удалось загрузить выдачи линз",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isPatientsError) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка загрузки пациентов",
|
||||||
|
description: "Не удалось загрузить список пациентов",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isLensesError) {
|
||||||
|
notification.error({
|
||||||
|
message: "Ошибка загрузки линз",
|
||||||
|
description: "Не удалось загрузить список линз",
|
||||||
|
placement: "topRight",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isIssuesError, isPatientsError, isLensesError, issuesError]);
|
||||||
|
|
||||||
|
const startFilterDateConverted = startFilterDate ? dayjs(startFilterDate) : null;
|
||||||
|
const endFilterDateConverted = endFilterDate ? dayjs(endFilterDate) : null;
|
||||||
|
const filterDates = [startFilterDateConverted, endFilterDateConverted];
|
||||||
|
const isFilterDates = startFilterDate && endFilterDate;
|
||||||
|
|
||||||
|
const containerStyle = {padding: 20};
|
||||||
|
const filterBarStyle = {marginBottom: 20};
|
||||||
|
const formItemStyle = {width: "100%"};
|
||||||
|
const viewModIconStyle = {marginRight: 8};
|
||||||
|
const advancedSearchCardStyle = {
|
||||||
|
marginBottom: 20,
|
||||||
|
boxShadow: "0 1px 6px rgba(0, 0, 0, 0.15)",
|
||||||
|
borderRadius: 8
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetTempSearchText = (value) => {
|
||||||
|
setTempSearchText(value);
|
||||||
|
dispatch(setCurrentPage(1)); // Сбрасываем на первую страницу
|
||||||
|
refetch(); // Обновляем данные при изменении поиска
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearSearch = () => {
|
||||||
|
setTempSearchText('');
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
|
||||||
|
const handleSetCurrentPage = (page) => {
|
||||||
|
dispatch(setCurrentPage(page));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetPageSize = (size) => {
|
||||||
|
dispatch(setPageSize(size));
|
||||||
|
};
|
||||||
|
const handleCloseModal = () => dispatch(closeModal());
|
||||||
|
const handleSelectIssue = (issue) => dispatch(selectIssue(issue));
|
||||||
|
const resetSelectedIssue = () => dispatch(selectIssue(null));
|
||||||
|
|
||||||
|
const handleAddIssue = () => {
|
||||||
|
dispatch(selectIssue(null));
|
||||||
|
dispatch(openModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePaginationChange = (page, pageSize) => {
|
||||||
|
handleSetCurrentPage(page);
|
||||||
|
handleSetPageSize(pageSize);
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterDateChange = (dates) => {
|
||||||
|
if (dates) {
|
||||||
|
const [start, end] = dates;
|
||||||
|
dispatch(setStartFilterDate(start.toISOString()));
|
||||||
|
dispatch(setEndFilterDate(end.toISOString()));
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetFilterDate = () => {
|
||||||
|
dispatch(setStartFilterDate(null));
|
||||||
|
dispatch(setEndFilterDate(null));
|
||||||
|
dispatch(setCurrentPage(1));
|
||||||
|
refetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
current: currentPage,
|
||||||
|
pageSize,
|
||||||
|
total: issuesData.total_count,
|
||||||
|
showSizeChanger: true,
|
||||||
|
pageSizeOptions: ["5", "10", "20", "50"],
|
||||||
|
onChange: handlePaginationChange,
|
||||||
|
onShowSizeChange: handlePaginationChange,
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmitFormModal = async (issueDate, patientId, lensId) => {
|
const handleSubmitFormModal = async (issueDate, patientId, lensId) => {
|
||||||
dispatch(closeModal());
|
dispatch(closeModal());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addIssue({issue_date: issueDate, patient_id: patientId, lens_id: lensId});
|
const formattedIssueDate = dayjs(issueDate).format('YYYY-MM-DD');
|
||||||
|
await addIssue({issue_date: formattedIssueDate, patient_id: patientId, lens_id: lensId}).unwrap();
|
||||||
notification.success({
|
notification.success({
|
||||||
message: "Линза выдана",
|
message: "Линза выдана",
|
||||||
description: "Линза успешно выдана пациенту.",
|
description: "Линза успешно выдана пациенту.",
|
||||||
placement: "topRight",
|
placement: "topRight",
|
||||||
});
|
});
|
||||||
|
refetch();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Add lens issue error:', error);
|
||||||
notification.error({
|
notification.error({
|
||||||
message: "Ошибка выдачи линзы",
|
message: "Ошибка выдачи линзы",
|
||||||
description: error?.data?.detail || "Не удалось выдать линзу пациенту.",
|
description: error?.data?.detail || "Не удалось выдать линзу пациенту.",
|
||||||
@ -32,10 +187,37 @@ const useIssues = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
issues,
|
issues: issuesData.issues,
|
||||||
|
total_count: issuesData.total_count,
|
||||||
|
patients,
|
||||||
|
lenses,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
isProcessing: isAdding,
|
||||||
|
tempSearchText, // Возвращаем только tempSearchText
|
||||||
|
selectedIssue,
|
||||||
|
isModalVisible,
|
||||||
|
viewMode,
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
|
filterDates,
|
||||||
|
isFilterDates,
|
||||||
|
containerStyle,
|
||||||
|
filterBarStyle,
|
||||||
|
formItemStyle,
|
||||||
|
viewModIconStyle,
|
||||||
|
advancedSearchCardStyle,
|
||||||
|
pagination,
|
||||||
|
handleAddIssue,
|
||||||
|
handlePaginationChange,
|
||||||
|
handleSetTempSearchText,
|
||||||
|
handleClearSearch,
|
||||||
|
handleSetViewMode,
|
||||||
|
handleCloseModal,
|
||||||
|
handleFilterDateChange,
|
||||||
|
resetSelectedIssue,
|
||||||
|
handleSelectIssue,
|
||||||
|
handleResetFilterDate,
|
||||||
handleSubmitFormModal,
|
handleSubmitFormModal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,147 +0,0 @@
|
|||||||
import {useDispatch, useSelector} from "react-redux";
|
|
||||||
import {getCachedInfo} from "../../../Utils/cachedInfoUtils.js";
|
|
||||||
import {
|
|
||||||
closeModal,
|
|
||||||
openModal,
|
|
||||||
selectIssue,
|
|
||||||
setCurrentPage,
|
|
||||||
setEndFilterDate,
|
|
||||||
setPageSize,
|
|
||||||
setSearchText,
|
|
||||||
setStartFilterDate,
|
|
||||||
setViewMode
|
|
||||||
} from "../../../Redux/Slices/lensIssuesSlice.js";
|
|
||||||
import {useEffect, useMemo} from "react";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
|
|
||||||
|
|
||||||
const useIssuesUI = (issues) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const {
|
|
||||||
searchText,
|
|
||||||
currentPage,
|
|
||||||
pageSize,
|
|
||||||
selectedIssue,
|
|
||||||
isModalVisible,
|
|
||||||
viewMode,
|
|
||||||
startFilterDate,
|
|
||||||
endFilterDate,
|
|
||||||
} = useSelector(state => state.lensIssuesUI);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.title = "Выдача линз";
|
|
||||||
const cachedViewMode = getCachedInfo("viewModeIssues");
|
|
||||||
if (cachedViewMode) dispatch(setViewMode(cachedViewMode));
|
|
||||||
}, [dispatch])
|
|
||||||
|
|
||||||
const startFilterDateConverted = startFilterDate ? dayjs(startFilterDate) : null;
|
|
||||||
const endFilterDateConverted = endFilterDate ? dayjs(endFilterDate) : null;
|
|
||||||
const filterDates = [startFilterDateConverted, endFilterDateConverted];
|
|
||||||
const isFilterDates = startFilterDate && endFilterDate;
|
|
||||||
|
|
||||||
const containerStyle = { padding: 20 };
|
|
||||||
const filterBarStyle = { marginBottom: 20 };
|
|
||||||
const formItemStyle = { width: "100%" };
|
|
||||||
const viewModIconStyle = { marginRight: 8 };
|
|
||||||
const advancedSearchCardStyle = {
|
|
||||||
marginBottom: 20,
|
|
||||||
boxShadow: "0 1px 6px rgba(0, 0, 0, 0.15)",
|
|
||||||
borderRadius: 8
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSetSearchText = (value) => dispatch(setSearchText(value));
|
|
||||||
const handleSetViewMode = (mode) => dispatch(setViewMode(mode));
|
|
||||||
const handleSetCurrentPage = (page) => dispatch(setCurrentPage(page));
|
|
||||||
const handleSetPageSize = (size) => dispatch(setPageSize(size));
|
|
||||||
const handleCloseModal = () => dispatch(closeModal());
|
|
||||||
const handleSelectIssue = (issue) => dispatch(selectIssue(issue));
|
|
||||||
const resetSelectedIssue = () => dispatch(selectIssue(null));
|
|
||||||
|
|
||||||
const handleAddIssue = () => {
|
|
||||||
dispatch(selectIssue(null));
|
|
||||||
dispatch(openModal());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePaginationChange = (page, pageSize) => {
|
|
||||||
handleSetCurrentPage(page);
|
|
||||||
handleSetPageSize(pageSize);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilterDateChange = (dates) => {
|
|
||||||
if (dates) {
|
|
||||||
const [start, end] = dates;
|
|
||||||
dispatch(setStartFilterDate(start.toISOString()));
|
|
||||||
dispatch(setEndFilterDate(end.toISOString()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResetFilterDate = () => {
|
|
||||||
dispatch(setStartFilterDate(null));
|
|
||||||
dispatch(setEndFilterDate(null));
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredIssues = useMemo(() => {
|
|
||||||
return issues.filter(issue => {
|
|
||||||
let dateFilter = true;
|
|
||||||
|
|
||||||
if (startFilterDateConverted && endFilterDateConverted) {
|
|
||||||
const issueDate = dayjs(issue.issue_date);
|
|
||||||
|
|
||||||
dateFilter = issueDate.isAfter(startFilterDateConverted) && issueDate.isBefore(endFilterDateConverted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
(
|
|
||||||
issue.patient.last_name.toLowerCase().includes(searchText) ||
|
|
||||||
issue.patient.first_name.toLowerCase().includes(searchText) ||
|
|
||||||
issue.doctor.last_name.toLowerCase().includes(searchText) ||
|
|
||||||
issue.doctor.first_name.toLowerCase().includes(searchText)
|
|
||||||
) &&
|
|
||||||
dateFilter
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}, [issues, searchText, startFilterDateConverted, endFilterDateConverted]);
|
|
||||||
|
|
||||||
const pagination = {
|
|
||||||
current: currentPage,
|
|
||||||
pageSize: pageSize,
|
|
||||||
showSizeChanger: true,
|
|
||||||
pageSizeOptions: ["5", "10", "20", "50"],
|
|
||||||
onChange: (page, newPageSize) => {
|
|
||||||
setCurrentPage(page);
|
|
||||||
setPageSize(newPageSize);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
filteredIssues,
|
|
||||||
pagination,
|
|
||||||
searchText,
|
|
||||||
selectedIssue,
|
|
||||||
isModalVisible,
|
|
||||||
viewMode,
|
|
||||||
currentPage,
|
|
||||||
pageSize,
|
|
||||||
filterDates,
|
|
||||||
isFilterDates,
|
|
||||||
containerStyle,
|
|
||||||
filterBarStyle,
|
|
||||||
formItemStyle,
|
|
||||||
viewModIconStyle,
|
|
||||||
advancedSearchCardStyle,
|
|
||||||
handleAddIssue,
|
|
||||||
handlePaginationChange,
|
|
||||||
handleSetSearchText,
|
|
||||||
handleSetViewMode,
|
|
||||||
handleSetCurrentPage,
|
|
||||||
handleSetPageSize,
|
|
||||||
handleCloseModal,
|
|
||||||
handleFilterDateChange,
|
|
||||||
resetSelectedIssue,
|
|
||||||
handleSelectIssue,
|
|
||||||
handleResetFilterDate,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useIssuesUI;
|
|
||||||
@ -37,11 +37,13 @@ const PatientsPage = () => {
|
|||||||
title: "Фамилия",
|
title: "Фамилия",
|
||||||
dataIndex: "last_name",
|
dataIndex: "last_name",
|
||||||
key: "last_name",
|
key: "last_name",
|
||||||
|
sorter: (a, b) => a.last_name.localeCompare(b.last_name),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Имя",
|
title: "Имя",
|
||||||
dataIndex: "first_name",
|
dataIndex: "first_name",
|
||||||
key: "first_name",
|
key: "first_name",
|
||||||
|
sorter: (a, b) => a.first_name.localeCompare(b.first_name),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Отчество",
|
title: "Отчество",
|
||||||
@ -52,6 +54,7 @@ const PatientsPage = () => {
|
|||||||
title: "Дата рождения",
|
title: "Дата рождения",
|
||||||
dataIndex: "birthday",
|
dataIndex: "birthday",
|
||||||
render: patientsData.formatDate,
|
render: patientsData.formatDate,
|
||||||
|
sorter: (a, b) => new Date(b.birthday) - new Date(a.birthday),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Телефон",
|
title: "Телефон",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user