Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить доску"
description="Попробуйте обновить доску. Если ошибка повторится, обновите страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { notFound } from 'next/navigation';
import { BoardsPage } from 'pages/boards';
import { BoardsPage } from 'pages/project/boards';

export default async function Page({
params,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить настройки доски"
description="Попробуйте обновить настройки доски. Если ошибка повторится, обновите страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
2 changes: 1 addition & 1 deletion app/(protected)/team/projects/[projectSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { notFound } from 'next/navigation';
import { BoardsPage } from 'pages/boards';
import { BoardsPage } from 'pages/project/boards';

export default async function Page({ params }: { params: Promise<{ projectSlug: string }> }) {
const { projectSlug } = await params;
Expand Down
20 changes: 20 additions & 0 deletions app/(protected)/team/projects/[projectSlug]/settings/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить настройки проекта"
description="Попробуйте обновить настройки проекта. Если ошибка повторится, обновите страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { notFound } from 'next/navigation';
import { ProjectSettingsPage } from 'pages/project/settings';

export default async function Page({ params }: { params: Promise<{ projectSlug: string }> }) {
const { projectSlug } = await params;
Expand All @@ -7,5 +8,5 @@ export default async function Page({ params }: { params: Promise<{ projectSlug:
return notFound();
}

return <div>конфиг проекта {projectSlug}</div>;
return <ProjectSettingsPage projectSlug={projectSlug} />;
}
20 changes: 20 additions & 0 deletions app/(protected)/team/projects/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить проекты"
description="Попробуйте обновить список проектов. Если ошибка повторится, обновите страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
20 changes: 20 additions & 0 deletions app/public/[teamId]/[projectSlug]/[boardSlug]/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';

import { ErrorState } from 'widgets/error-state';

export default function Error({
unstable_retry,
}: {
error: Error & { digest?: string };
unstable_retry: () => void;
}) {
return (
<ErrorState
title="Не удалось загрузить доску"
description="Попробуйте обновить доску. Если ошибка повторится, обновите страницу."
actionLabel="Попробовать снова"
onRetry={() => unstable_retry()}
className="border"
/>
);
}
16 changes: 16 additions & 0 deletions app/public/[teamId]/[projectSlug]/[boardSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { notFound } from 'next/navigation';
import { BoardsPage } from 'pages/project/boards';

export default async function Page({
params,
}: {
params: Promise<{ boardSlug: string; projectSlug: string }>;
}) {
const { boardSlug, projectSlug } = await params;

if (!boardSlug || !projectSlug) {
return notFound();
}

return <BoardsPage projectSlug={projectSlug} boardSlug={boardSlug} />;
}
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
import './.next/dev/types/routes.d.ts';
import './.next/types/routes.d.ts';

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
34 changes: 23 additions & 11 deletions src/entities/board/api/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type * as TBoard from '../model/types';
export class BoardHttp {
static getBoardList(projectSlug: string, signal?: AbortSignal) {
return api<TBoard.BoardListResponse>({
url: `/projects/${projectSlug}/area`,
url: `/projects/${projectSlug}/areas`,
method: 'GET',
contracts: {
response: SBoard.BoardListResponse,
Expand All @@ -16,7 +16,7 @@ export class BoardHttp {

static getBoard(projectSlug: string, id: string, signal?: AbortSignal) {
return api<TBoard.BoardResponse>({
url: `/projects/${projectSlug}/area/${id}`,
url: `/projects/${projectSlug}/areas/${id}`,
method: 'GET',
contracts: {
response: SBoard.Board,
Expand All @@ -27,7 +27,7 @@ export class BoardHttp {

static createBoard(projectSlug: string, data: TBoard.CreateBoardBody) {
return api<TBoard.CreateBoardResponse>({
url: `/projects/${projectSlug}/area`,
url: `/projects/${projectSlug}/areas`,
method: 'POST',
data,
contracts: {
Expand All @@ -39,7 +39,7 @@ export class BoardHttp {

static updateBoard(projectSlug: string, boardSlug: string, data: TBoard.UpdateBoardBody) {
return api<TBoard.ActionResponse>({
url: `/projects/${projectSlug}/area/${boardSlug}`,
url: `/projects/${projectSlug}/areas/${boardSlug}`,
method: 'PUT',
data,
contracts: {
Expand All @@ -51,7 +51,7 @@ export class BoardHttp {

static removeBoard(projectSlug: string, boardSlug: string) {
return api<TBoard.ActionResponse>({
url: `/projects/${projectSlug}/area/${boardSlug}`,
url: `/projects/${projectSlug}/areas/${boardSlug}`,
method: 'DELETE',
contracts: {
response: SBoard.ActionResponse,
Expand All @@ -61,7 +61,7 @@ export class BoardHttp {

static getBoardColumnList(boardSlug: string, signal?: AbortSignal) {
return api<TBoard.BoardColumnListResponse>({
url: `/area/${boardSlug}/states`,
url: `/areas/${boardSlug}/states`,
method: 'GET',
contracts: {
response: SBoard.BoardColumnListResponse,
Expand All @@ -72,7 +72,7 @@ export class BoardHttp {

static getBoardColumn(boardSlug: string, columnId: string, signal?: AbortSignal) {
return api<TBoard.BoardColumnResponse>({
url: `/area/${boardSlug}/states/${columnId}`,
url: `/areas/${boardSlug}/states/${columnId}`,
method: 'GET',
contracts: {
response: SBoard.BoardColumn,
Expand All @@ -83,7 +83,7 @@ export class BoardHttp {

static createBoardColumn(boardSlug: string, data: TBoard.CreateBoardColumnBody) {
return api<TBoard.CreateBoardColumnResponse>({
url: `/area/${boardSlug}/states`,
url: `/areas/${boardSlug}/states`,
method: 'POST',
data,
contracts: {
Expand All @@ -99,7 +99,7 @@ export class BoardHttp {
data: TBoard.UpdateBoardColumnBody
) {
return api<TBoard.ActionResponse>({
url: `/area/${boardSlug}/states/${columnId}`,
url: `/areas/${boardSlug}/states/${columnId}`,
method: 'PATCH',
data,
contracts: {
Expand All @@ -111,7 +111,7 @@ export class BoardHttp {

static removeBoardColumn(boardSlug: string, columnId: string) {
return api<TBoard.ActionResponse>({
url: `/area/${boardSlug}/states/${columnId}`,
url: `/areas/${boardSlug}/states/${columnId}`,
method: 'DELETE',
contracts: {
response: SBoard.ActionResponse,
Expand All @@ -120,11 +120,23 @@ export class BoardHttp {
}
static restoreBoardColumn(boardSlug: string, columnId: string) {
return api<TBoard.ActionResponse>({
url: `/area/${boardSlug}/states/${columnId}/restore`,
url: `/areas/${boardSlug}/states/${columnId}/restore`,
method: 'POST',
contracts: {
response: SBoard.ActionResponse,
},
});
}

static moveBoardColumn(boardSlug: string, columnId: string, data: TBoard.MoveBoardColumnBody) {
return api<TBoard.ActionResponse>({
url: `/areas/${boardSlug}/states/${columnId}/move`,
method: 'POST',
data,
contracts: {
body: SBoard.MoveBoardColumnBody,
response: SBoard.ActionResponse,
},
});
}
}
1 change: 1 addition & 0 deletions src/entities/board/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { BoardHttp } from './api/http';
export { BoardQueries } from './api/queries';
export { BOARD_COLUMN_COLORS } from './config/colors';
export { calculateNewPosition } from './lib/calculate-new-position';
export { boardFabricKeys } from './model/consts';
export * as SBoard from './model/schemas';
export type * as TBoard from './model/types';
24 changes: 24 additions & 0 deletions src/entities/board/lib/calculate-new-position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const MIN_STEP = 10000000;

function isNumber(value: number | null): value is number {
return value !== null && !Number.isNaN(value);
}

function prepareResult(result: number): number {
return Math.round(result); // TODO: remove this after change schema to allow float
}

export function calculateNewPosition(
prevPosition: number | null,
nextPosition: number | null
): number {
if (!isNumber(prevPosition) && isNumber(nextPosition)) return prepareResult(nextPosition / 2);
if (isNumber(prevPosition) && !isNumber(nextPosition))
return prepareResult(prevPosition + MIN_STEP);

if (isNumber(prevPosition) && isNumber(nextPosition)) {
return prepareResult((prevPosition + nextPosition) / 2);
}

return MIN_STEP;
}
20 changes: 11 additions & 9 deletions src/entities/board/model/schemas.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createSortingSchema, CursorQuerySchema, DateTimeString, GlobalSuccess } from 'shared/api';
import { z } from 'zod/v4';
import { HEX_COLOR_REGEX } from 'shared/lib/utils';
import { z } from 'zod/v4';

export const ActionResponse = GlobalSuccess;

Expand Down Expand Up @@ -47,10 +47,7 @@ export const Board = z.object({
.int('Количество задач должно быть целым числом')
.min(0, 'Количество задач не может быть отрицательным'),
defaultView: ViewTypeEnum,
position: z
.number()
.int('Позиция должна быть целым числом')
.min(0, 'Позиция не может быть отрицательной'),
position: z.number().min(0, 'Позиция не может быть отрицательной'),
maxTasksLimit: z
.number()
.int('Лимит задач должен быть целым числом')
Expand Down Expand Up @@ -79,10 +76,7 @@ export const BoardColumn = z.object({
.nullable()
.optional(),
icon: z.string().max(20, 'Иконка должна быть не длиннее 20 символов').nullable().optional(),
position: z
.number()
.int('Позиция должна быть целым числом')
.min(0, 'Позиция не может быть отрицательной'),
position: z.number().min(0, 'Позиция не может быть отрицательной'),
isVisible: z.boolean(),
maxTasksLimit: z
.number()
Expand Down Expand Up @@ -164,6 +158,14 @@ export const UpdateBoardColumnBody = CreateBoardColumnBody.partial().refine(
}
);

export const MoveBoardColumnBody = z
.object({
position: z.number().nonnegative(),
prevStatePosition: z.number().nonnegative().nullable(),
nextStatePosition: z.number().nonnegative().nullable(),
})
.strict();

export const BoardColumnQueryParams = z
.object({
hidden: z.boolean().optional(),
Expand Down
1 change: 1 addition & 0 deletions src/entities/board/model/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type CreateBoardResponse = z.infer<typeof SBoard.CreateBoardResponse>;

export type CreateBoardColumnBody = z.infer<typeof SBoard.CreateBoardColumnBody>;
export type UpdateBoardColumnBody = z.infer<typeof SBoard.UpdateBoardColumnBody>;
export type MoveBoardColumnBody = z.infer<typeof SBoard.MoveBoardColumnBody>;
export type CreateBoardColumnResponse = z.infer<typeof SBoard.CreateBoardColumnResponse>;

export type ActionResponse = z.infer<typeof SBoard.ActionResponse>;
2 changes: 1 addition & 1 deletion src/entities/project/api/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { queryOptions } from '@tanstack/react-query';
import { projectFabricKeys } from '../model/const';
import { projectFabricKeys } from '../model/consts';
import { ProjectHttp } from './http';

export class ProjectQueries {
Expand Down
2 changes: 1 addition & 1 deletion src/entities/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * as SProject from './model/schemas';
export type * as TProject from './model/types';
export { ProjectHttp } from './api/http';
export { ProjectQueries } from './api/queries';
export { projectFabricKeys } from './model/const';
export { projectFabricKeys } from './model/consts';
export { PROJECT_ICONS } from './config/icons';
export { PROJECT_COLORS } from './config/colors';
export { projectIconCodeToEmoji } from './lib/emoji';
Expand Down
2 changes: 1 addition & 1 deletion src/entities/project/lib/useCheckSlug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
import type { FieldErrors } from 'react-hook-form';
import { debounce } from 'shared/lib/utils';
import { ProjectQueries } from '../api/queries';
import { MAX_SLUG_LENGTH, MIN_SLUG_LENGTH, projectFabricKeys } from '../model/const';
import { MAX_SLUG_LENGTH, MIN_SLUG_LENGTH, projectFabricKeys } from '../model/consts';

const DEBOUNCE_MS = 400;
const SLUG_UNAVAILABLE_MESSAGE = 'Этот адрес уже занят';
Expand Down
Loading
Loading