241 lines
6.9 KiB
Markdown
241 lines
6.9 KiB
Markdown
|
|
# Требования к коду
|
|||
|
|
|
|||
|
|
Используется библиотека `react-router-dom` и `react=markdown`.
|
|||
|
|
|
|||
|
|
Все компоненты должны быть функциональными.
|
|||
|
|
props должны быть сразу распакованы.
|
|||
|
|
export без default
|
|||
|
|
|
|||
|
|
Пример:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
export function ProfessionPage() {
|
|||
|
|
const {professionId} = useParams<{ professionId: string }>();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Используйте современные практики React (например, хуки).
|
|||
|
|
При создании новых компонентов добавьте комментарии.
|
|||
|
|
|
|||
|
|
Если для задачи необходимы новые типы, интерфейсы или вспомогательные компоненты - создавай их в соответвующих файлах.
|
|||
|
|
|
|||
|
|
# Документария
|
|||
|
|
|
|||
|
|
## Существующие типы
|
|||
|
|
|
|||
|
|
`types/api.ts`
|
|||
|
|
```ts
|
|||
|
|
export interface ApiResponse {
|
|||
|
|
body?: object,
|
|||
|
|
success: boolean
|
|||
|
|
}
|
|||
|
|
export interface PageResponse<T>{
|
|||
|
|
count: number;
|
|||
|
|
next?: string;
|
|||
|
|
previous?: string;
|
|||
|
|
results: T[]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`types/common.ts`
|
|||
|
|
```ts
|
|||
|
|
export interface UniqueItem{
|
|||
|
|
id: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface PageControl<T>{
|
|||
|
|
items: T[] | null;
|
|||
|
|
hasNext: boolean;
|
|||
|
|
hasPrevious: boolean;
|
|||
|
|
nextPage(): void;
|
|||
|
|
previousPage(): void;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
`types/survey.ts`
|
|||
|
|
```ts
|
|||
|
|
import {UniqueItem} from "./common.ts";
|
|||
|
|
|
|||
|
|
export interface Survey extends UniqueItem{
|
|||
|
|
title: string;
|
|||
|
|
description: string;
|
|||
|
|
}
|
|||
|
|
interface Statement extends UniqueItem{
|
|||
|
|
text: string;
|
|||
|
|
score_variable: number;
|
|||
|
|
}
|
|||
|
|
export enum QuestionType{
|
|||
|
|
QuestionPair,
|
|||
|
|
AgreementQuestion
|
|||
|
|
}
|
|||
|
|
export interface Question extends UniqueItem{
|
|||
|
|
questionType: QuestionType
|
|||
|
|
}
|
|||
|
|
export interface QuestionPair extends Question{
|
|||
|
|
statements: Statement[]
|
|||
|
|
}
|
|||
|
|
export interface AgreementQuestion extends Question{
|
|||
|
|
question: string;
|
|||
|
|
score_variable: number;
|
|||
|
|
}
|
|||
|
|
export interface ScoreVariable {
|
|||
|
|
id: string;
|
|||
|
|
name: string;
|
|||
|
|
title: string;
|
|||
|
|
description: string;
|
|||
|
|
survey: number;
|
|||
|
|
}
|
|||
|
|
export interface Profession extends UniqueItem {
|
|||
|
|
name: string;
|
|||
|
|
description: string;
|
|||
|
|
}
|
|||
|
|
export interface Specialty extends UniqueItem {
|
|||
|
|
name: string;
|
|||
|
|
institution: number;
|
|||
|
|
link?: string;
|
|||
|
|
}
|
|||
|
|
export interface Institution extends UniqueItem {
|
|||
|
|
name: string;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
`types/users.ts`
|
|||
|
|
```ts
|
|||
|
|
import {UniqueItem} from "./common.ts";
|
|||
|
|
|
|||
|
|
export type UserContextType = {
|
|||
|
|
user: Account | undefined;
|
|||
|
|
login: (credentials: TokenObtainPair) => Promise<void>;
|
|||
|
|
logout: () => void;
|
|||
|
|
};
|
|||
|
|
export interface ContactData{
|
|||
|
|
first_name: string;
|
|||
|
|
last_name?: string;
|
|||
|
|
middle_name?: string;
|
|||
|
|
phone?: string;
|
|||
|
|
email?:string;
|
|||
|
|
}
|
|||
|
|
interface Role extends UniqueItem{
|
|||
|
|
title: string;
|
|||
|
|
}
|
|||
|
|
export interface Account extends UniqueItem, ContactData{
|
|||
|
|
login: string;
|
|||
|
|
role?: Role;
|
|||
|
|
is_staff: boolean;
|
|||
|
|
is_active: boolean;
|
|||
|
|
}
|
|||
|
|
export interface TokenObtainPair {
|
|||
|
|
login: string;
|
|||
|
|
password: string;
|
|||
|
|
}
|
|||
|
|
export interface TokenRefresh {
|
|||
|
|
refresh: string;
|
|||
|
|
}
|
|||
|
|
export interface TokenVerify {
|
|||
|
|
token: string;
|
|||
|
|
}
|
|||
|
|
export interface JwtTokenResponse {
|
|||
|
|
access: string;
|
|||
|
|
refresh: string;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Способы запросов
|
|||
|
|
|
|||
|
|
Вместо fetch запросы должны проходить при помощи специальных функций/функции
|
|||
|
|
|
|||
|
|
`API/common.ts`
|
|||
|
|
```ts
|
|||
|
|
async function getFetch(path: string) // Get method
|
|||
|
|
async function postFetch(
|
|||
|
|
path: string,
|
|||
|
|
body: object | FormData
|
|||
|
|
) // Post method
|
|||
|
|
async function patchFetch(
|
|||
|
|
path: string,
|
|||
|
|
body: object | FormData
|
|||
|
|
) // Patch method
|
|||
|
|
async function deleteFetch(path: string) // Delete method
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Хуки
|
|||
|
|
|
|||
|
|
### Для загрузки данных
|
|||
|
|
|
|||
|
|
Для упрощения работы с api созданы следующие хуки
|
|||
|
|
|
|||
|
|
`API/hooks.ts`
|
|||
|
|
```ts
|
|||
|
|
function useDataPage<T>(application:string, endpoint: string, params:{[key:string]:string|number}={}, needLoad:boolean=true):PageControl<T>
|
|||
|
|
function useData<T>(application:string, endpoint: string, id: string|number, needLoad:boolean=true): T|null|undefined
|
|||
|
|
function useCachedData<T>(application: string, endpoint: string): (id: string | number, needLoad: boolean = true)=> T|undefined|null
|
|||
|
|
```
|
|||
|
|
- application - Название модуля
|
|||
|
|
- endpoint - Название объекта над которым идёт работа
|
|||
|
|
- id - id искомого объекта
|
|||
|
|
- params - дополнительные параметры (например фильтрация)
|
|||
|
|
- needLoad - указывает нужно ли загружать список/объект прямо сейчас
|
|||
|
|
|
|||
|
|
|
|||
|
|
- null - загрузка еще не происходила
|
|||
|
|
- undefined - не найдено
|
|||
|
|
- T (объект)
|
|||
|
|
|
|||
|
|
### Для ленивой загрузки элементов
|
|||
|
|
|
|||
|
|
`components/LoadingData.tsx`
|
|||
|
|
```tsx
|
|||
|
|
function LoadingData({data, children}: {data: object|null|undefined, children: (ReactElement | null | undefined | boolean)[]})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для загрузки отдельного элемента.
|
|||
|
|
Если data
|
|||
|
|
- null - Возвращает <div className={"loading"}>Идёт загрузка...</div>
|
|||
|
|
- undefined - <Link className={"not-found"} to={"/"}>404 НЕ НАЙДЕНО</Link>
|
|||
|
|
- иначе возвращает children
|
|||
|
|
|
|||
|
|
`components/LoadingList.tsx`
|
|||
|
|
```tsx
|
|||
|
|
function LoadingList({data, listElement}: {data: UniqueItem[]|null, listElement(e:UniqueItem, i:number):unknown})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Для загрузки списка. Если data не null - то возвращает элементы результаты `listElement`
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
export function ProfessionPage() {
|
|||
|
|
const { professionId } = useParams<{ professionId: string }>();
|
|||
|
|
const profession = useData<Profession>('surveys', 'professions', professionId || 0);
|
|||
|
|
const specialties = useDataPage<Specialty>('surveys', 'specialties', { profession: professionId || 0 }, !!profession);
|
|||
|
|
|
|||
|
|
const institutions = useCachedData<Institution>("surveys", "institutions");
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<LoadingData data={profession}>
|
|||
|
|
<h1>{profession?.name}</h1>
|
|||
|
|
<div className="description">
|
|||
|
|
<ReactMarkdown>{profession?.description || ''}</ReactMarkdown>
|
|||
|
|
</div>
|
|||
|
|
<div className="specialties-block">
|
|||
|
|
{specialties.items != null ? <h2>Где можно обучиться</h2>:null}
|
|||
|
|
<LoadingList
|
|||
|
|
data={specialties.items}
|
|||
|
|
listElement={specialty => (
|
|||
|
|
<SpecialtyBlock
|
|||
|
|
key={specialty.id}
|
|||
|
|
specialty={specialty as Specialty}
|
|||
|
|
institution={institutions((specialty as Specialty).institution)}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</LoadingData>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
### Авторизация
|
|||
|
|
|
|||
|
|
Везде доступен хук `useUser():UserContextType | undefined `
|