Skip to content
Merged
1 change: 1 addition & 0 deletions packages/common.types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ export type { InvitationDataT } from './src/invitations';
export type { GroupStudentsListSchema } from './src/students';
export type { ContactT, ContactsT } from './src/contacts';
export type { NotificationKind } from './src/notifications';
export type { ScreenSizeT } from './src/screenSize';
1 change: 1 addition & 0 deletions packages/common.types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ export type {
export type { SignupData } from './auth';
export type { ContactT, ContactsT } from './contacts';
export type { NotificationKind } from './notifications';
export type { ScreenSizeT } from './screenSize';
1 change: 1 addition & 0 deletions packages/common.types/src/screenSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ScreenSizeT = 'mobile' | 'tablet' | 'desktop';
1 change: 1 addition & 0 deletions packages/common.utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export { useMedia } from './src/useMedia';
export { useNetworkStatus, NetworkProvider } from './src/NetworkContext';
export { useRetryQueue } from './src/useRetryQueue';
export { getUserAvatarUrl } from './src/getUserAvatarUrl';
export { useScreenSize } from './src/useScreenSize';
1 change: 1 addition & 0 deletions packages/common.utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@xipkg/eslint": "3.2.0",
"@xipkg/typescript": "latest",
"common.eslint": "*",
"common.types": "*",
"eslint": "^9.19.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18",
Expand Down
11 changes: 11 additions & 0 deletions packages/common.utils/src/useScreenSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useMedia } from './useMedia';
import type { ScreenSizeT } from 'common.types';

export const useScreenSize = (): ScreenSizeT => {
const isMobile = useMedia('(max-width: 720px)');
const isTablet = useMedia('(max-width: 960px)');

if (isMobile) return 'mobile';
if (isTablet) return 'tablet';
return 'desktop';
};
2 changes: 1 addition & 1 deletion packages/features.invoice.card/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { InvoiceCard } from './src';
export { InvoiceCard, StatusBadge } from './src';
2 changes: 1 addition & 1 deletion packages/features.invoice.card/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { InvoiceCard } from './ui/InvoiceCard';
export { InvoiceCard, StatusBadge } from './ui';
2 changes: 2 additions & 0 deletions packages/features.invoice.card/src/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { InvoiceCard } from './InvoiceCard';
export { StatusBadge } from './components';
4 changes: 3 additions & 1 deletion packages/features.payment.approve/src/types/PaymentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export type ApprovePaymentPropsT = {
id?: number;
};

export type PaymentApproveActionPropsT = Pick<ApprovePaymentPropsT, 'payment' | 'isTutor'>;
export type PaymentApproveActionPropsT = Pick<ApprovePaymentPropsT, 'payment' | 'isTutor'> & {
type?: InvoiceCardTypeT;
};

export type PaymentApproveButtonPropsT = Omit<ApprovePaymentPropsT, 'payment'> & {
type?: InvoiceCardTypeT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useState } from 'react';
import { PaymentApproveButton, PaymentApproveModal } from '.';
import { PaymentApproveActionPropsT } from '../types';

export const PaymentApproveAction = ({ payment, isTutor }: PaymentApproveActionPropsT) => {
export const PaymentApproveAction = ({ payment, isTutor, type }: PaymentApproveActionPropsT) => {
const [isOpen, setIsOpen] = useState(false);

const handleModalState = () => setIsOpen((prev) => !prev);
Expand All @@ -17,6 +17,7 @@ export const PaymentApproveAction = ({ payment, isTutor }: PaymentApproveActionP
isTutor={isTutor}
id={payment.id}
classroomId={payment.classroom_id}
type={type}
/>
{isOpen && payment && (
<PaymentApproveModal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ export const PaymentApproveButton = ({

if (status === 'wf_sender_confirmation')
return (
<div
className={`flex flex-row items-center justify-between gap-4 ${type === 'table' ? 'w-full sm:w-auto' : ''} `}
>
<div className="flex flex-row items-center justify-between gap-4">
<Tooltip delayDuration={1000}>
<TooltipTrigger asChild>
<Button
Expand Down Expand Up @@ -49,7 +47,7 @@ export const PaymentApproveButton = ({
<Button
variant="ghost"
size="s"
className="bg-brand-0 hover:bg-brand-0/80 rounded-lg"
className="bg-brand-0 hover:bg-brand-0/80 flex-1 rounded-lg"
onClick={() => receiverConfirmationMutation(id?.toString() ?? '')}
loading={isPending}
disabled={isPending}
Expand Down
3 changes: 2 additions & 1 deletion packages/features.table/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"common.api": "*",
"common.services": "*",
"common.types": "*",
"features.payment.approve": "*"
"features.payment.approve": "*",
"features.invoice.card": "*"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
Expand Down
15 changes: 7 additions & 8 deletions packages/features.table/src/ui/cells/StatusCell.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { mapPaymentStatus } from 'common.types';
import { PaymentApproveAction, ApprovePaymentPropsT } from 'features.payment.approve';
import { StatusBadge } from 'features.invoice.card';

export const StatusCell = ({ payment, status, isTutor = false }: ApprovePaymentPropsT) => {
const statusText = mapPaymentStatus[status];

if (!statusText) {
throw new Error('Paiment Status not found');
export const StatusCell = ({ payment, isTutor = false }: ApprovePaymentPropsT) => {
if (!payment) {
return null;
}

return (
<div className="flex flex-row items-center justify-between gap-8">
<PaymentApproveAction payment={payment} isTutor={isTutor} />
<div className="flex flex-row items-center justify-between gap-4">
<StatusBadge status={payment.status} className="p-0" />
<PaymentApproveAction payment={payment} isTutor={isTutor} type="table" />
</div>
);
};
23 changes: 12 additions & 11 deletions packages/features.table/src/utils/createPaymentColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import {
StatusCell,
ActionsCell,
} from '../ui/cells';
import { ScreenSizeT } from 'common.types';

type ColumnArgsT<Role extends RoleT = RoleT> = {
usersRole: RoleT;
isMobile?: boolean;
screenSize?: ScreenSizeT;
onApprovePayment?: (payment: RolePaymentT<Role>) => void;
withStudentColumn?: boolean;
isTutor?: boolean;
Expand All @@ -22,13 +23,14 @@ export const createPaymentColumns = <Role extends RoleT>({
usersRole,
onApprovePayment,
isTutor,
screenSize,
}: ColumnArgsT<Role>): ColumnDef<RolePaymentT<Role>>[] => {
const baseColumns: (ColumnDef<RolePaymentT<Role>> | false)[] = [
{
accessorKey: 'created_at',
header: 'Дата',
cell: ({ row }) => <DateCell date={row.original.created_at} />,
size: 82,
size: 96,
filterFn: (row, columnId, value) => {
const date = new Date(row.getValue(columnId)).getTime();
const [from, to] = value;
Expand Down Expand Up @@ -56,7 +58,7 @@ export const createPaymentColumns = <Role extends RoleT>({
/>
);
},
size: 160,
size: 180,
filterFn: (row, columnId, value) => value.includes(row.getValue(columnId)),
enableColumnFilter: true,
};
Expand Down Expand Up @@ -87,7 +89,7 @@ export const createPaymentColumns = <Role extends RoleT>({
},
enableColumnFilter: true,
},
{
screenSize === 'desktop' && {
accessorKey: 'payment_type',
header: 'Тип оплаты',
cell: ({ row }) => <TypePaymentCell paymentType={row.original.payment_type} />,
Expand All @@ -107,18 +109,17 @@ export const createPaymentColumns = <Role extends RoleT>({
onApprovePayment={() => onApprovePayment?.(row.original)}
/>
),
size: 220,
size: screenSize === 'desktop' ? 260 : 160,
filterFn: (row, columnId, value) => value.includes(row.getValue(columnId)),
enableColumnFilter: true,
},
{
usersRole === 'student' && {
accessorKey: 'actions',
header: '',
cell: ({ row }) =>
usersRole === 'student' ? (
<ActionsCell invoiceId={row.original.id} classroomId={row.original.classroom_id} />
) : null,
size: usersRole === 'student' ? 96 : 0,
cell: ({ row }) => (
<ActionsCell invoiceId={row.original.id} classroomId={row.original.classroom_id} />
),
size: screenSize === 'desktop' ? 96 : 40,
filterFn: (row, columnId, value) => value.includes(row.getValue(columnId)),
enableColumnFilter: false,
},
Expand Down
2 changes: 1 addition & 1 deletion packages/pages.classroom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@xipkg/tabs": "2.1.0",
"@xipkg/tooltip": "2.1.0",
"@xipkg/userprofile": "4.0.14",
"@xipkg/utils": "^1.8.0",
"common.utils": "*",
"common.api": "*",
"common.entities": "*",
"common.services": "*",
Expand Down
14 changes: 1 addition & 13 deletions packages/pages.classroom/src/ui/ClassroomPage.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import { Button } from '@xipkg/button';
import { Plus } from '@xipkg/icons';

import { Header } from './Header';
import { Tabs } from './Tabs';

export const ClassroomPage = () => {
return (
<div className="flex h-full flex-col justify-between gap-6 overflow-y-auto pr-4 max-md:pl-4">
<div className="flex h-full flex-col justify-between gap-6 overflow-y-auto max-md:pl-4 sm:pr-4">
<div className="flex flex-col pt-1">
<Header />
<Tabs />
</div>

<div className="xs:hidden flex flex-row items-center justify-end">
<Button
size="small"
className="fixed right-4 bottom-4 z-50 flex h-12 w-12 items-center justify-center rounded-xl"
>
<Plus className="fill-brand-0" />
</Button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const Content = ({ classroom }: ContentProps) => {
}, [search, handleCallClick]);

return (
<div className="flex flex-row items-start pl-4">
<div className="flex flex-row items-start pr-4 pl-4 sm:pr-0">
<div className="flex flex-col items-start gap-4">
{classroom.kind === 'individual' ? (
<IndividualUser userId={classroom.student_id ?? classroom.tutor_id ?? 0} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export const Information = ({ classroom }: { classroom: ClassroomT }) => {
}, [form, onSubmit]);

return (
<div className="flex flex-col gap-4 md:flex-row">
<div className="flex flex-col gap-4 pr-4 sm:pr-0 md:flex-row">
<div className="order-2 flex h-full w-full flex-1 justify-center md:order-1">
<InformationNote classroom={classroom} note={note} />
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/pages.classroom/src/ui/Payments/Payments.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useMemo, useRef } from 'react';
import { useInfiniteQuery, createPaymentColumns } from 'features.table';
import { VirtualizedPaymentsTable } from 'pages.payments';
import { useMediaQuery } from '@xipkg/utils';
import { useScreenSize } from 'common.utils';
import { useParams } from '@tanstack/react-router';
import { useGetClassroom, useCurrentUser } from 'common.services';

export const Payments = () => {
const { classroomId } = useParams({ from: '/(app)/_layout/classrooms/$classroomId/' });
const { data: classroom } = useGetClassroom(Number(classroomId));
const isMobile = useMediaQuery('(max-width: 719px)');
const screenSize = useScreenSize();

const parentRef = useRef<HTMLDivElement>(null);

Expand All @@ -27,10 +27,10 @@ export const Payments = () => {
createPaymentColumns({
withStudentColumn: false,
usersRole: isTutor ? 'student' : 'tutor',
isMobile,
screenSize,
isTutor,
}),
[isMobile, isTutor],
[screenSize, isTutor],
);

if (isLoading) {
Expand Down
2 changes: 1 addition & 1 deletion packages/pages.classroom/src/ui/Tabs/TabsStudent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const TabsStudent = () => {

return (
<Tabs.Root value={currentTab} onValueChange={handleTabChange}>
<div className="flex h-[56px] flex-row items-center overflow-x-auto pl-4">
<div className="flex h-[56px] flex-row items-center overflow-x-auto pr-4 pl-4 sm:pr-0">
<Tabs.List className="flex flex-row gap-4">
<Tabs.Trigger value="overview" className="text-m-base font-medium text-gray-100">
Сводка
Expand Down
13 changes: 7 additions & 6 deletions packages/pages.classroom/src/ui/Tabs/TabsTutor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { useState } from 'react';
import { Tabs } from '@xipkg/tabs';
import { useSearch, useNavigate, useParams } from '@tanstack/react-router';

import { Plus } from '@xipkg/icons';
import { Button } from '@xipkg/button';
import { Overview } from '../Overview';
import { SearchParams } from '../../types/router';
Expand Down Expand Up @@ -40,7 +40,7 @@ export const TabsTutor = () => {

return (
<Tabs.Root value={currentTab} onValueChange={handleTabChange}>
<div className="flex h-[56px] flex-row items-center overflow-x-auto pl-4">
<div className="flex h-[56px] flex-row items-center gap-4 overflow-x-auto pr-4 pl-4 sm:pr-0">
<Tabs.List className="flex flex-row gap-4">
<Tabs.Trigger value="overview" className="text-m-base font-medium text-gray-100">
Сводка
Expand All @@ -60,14 +60,14 @@ export const TabsTutor = () => {
</Tabs.List>
{currentTab === 'overview' && classroom?.kind === 'group' && (
<ModalStudentsGroup>
<Button size="s" variant="ghost" className="ml-auto rounded-[8px]">
<Button size="s" variant="ghost" className="ml-auto rounded-lg">
Добавить ученика
</Button>
</ModalStudentsGroup>
)}
{currentTab === 'overview' && classroom?.kind === 'group' && (
<ModalGroupInvite>
<Button size="s" variant="ghost" className="ml-1 rounded-[8px]">
<Button size="s" variant="ghost" className="ml-1 rounded-lg">
Пригласить в группу
</Button>
</ModalGroupInvite>
Expand All @@ -76,10 +76,11 @@ export const TabsTutor = () => {
{currentTab === 'payments' && (
<Button
size="s"
className="ml-auto rounded-[8px]"
className="ml-auto w-8 rounded-lg px-2 py-2 font-medium sm:w-auto sm:px-4"
onClick={() => setIsInvoiceModalOpen(true)}
>
Создать счёт на оплату
<span className="hidden sm:flex">Создать счёт на оплату</span>
<Plus size="sm" className="fill-brand-0 flex sm:hidden" />
</Button>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/pages.payments/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './useResponsiveGrid';
export * from './useInfiniteScroll';
export * from './useVirtualGrid';
export * from './useVirtualCards';
37 changes: 37 additions & 0 deletions packages/pages.payments/src/hooks/useVirtualCards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RefObject, useLayoutEffect, useState, useCallback } from 'react';
import { useVirtualizer, type Virtualizer } from '@tanstack/react-virtual';

export const useVirtualCards = <T>(
parentRef: RefObject<HTMLDivElement | null>,
items: T[],
estimatedCardHeight = 182,
overscan = 8,
) => {
const [cardHeights, setCardHeights] = useState<Record<number, number>>({});

const virtualizer: Virtualizer<HTMLDivElement, Element> = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: (index) => cardHeights[index] ?? estimatedCardHeight,
overscan,
});

const measureCard = useCallback((index: number, el: HTMLDivElement | null) => {
if (!el) return;

const height = el.getBoundingClientRect().height;

setCardHeights((prev) => {
if (prev[index] === height) return prev;
return { ...prev, [index]: height };
});
}, []);

useLayoutEffect(() => {
if (Object.keys(cardHeights).length > 0) {
virtualizer.measure();
}
}, [cardHeights, virtualizer]);

return { virtualizer, measureCard };
};
Loading
Loading