Начало диплома Даны
This commit is contained in:
@@ -1,260 +1,40 @@
|
|||||||
# Требования к коду
|
# Введение
|
||||||
|
|
||||||
Используется библиотека `react-router-dom` и `react=markdown`.
|
В условиях динамичного развития рынка коммерческой недвижимости офисным центрам необходимо не только обеспечивать
|
||||||
|
высокий уровень обслуживания арендаторов, но и эффективно управлять внутренними процессами. Одним из ключевых
|
||||||
|
направлений деятельности является организация аренды помещений, планирование их загрузки,
|
||||||
|
своевременное обновление информации об услугах и надёжный контроль за бронированиями и платежами.
|
||||||
|
|
||||||
Все компоненты должны быть функциональными.
|
На практике многие офисные центры продолжают использовать устаревшие методы учёта, такие как бумажные документы или
|
||||||
props должны быть сразу распакованы.
|
таблицы Excel, что усложняет управление, увеличивает вероятность ошибок и снижает оперативность принятия решений.
|
||||||
export без default
|
Отсутствие единой информационной системы часто приводит к нарушению сроков аренды,
|
||||||
|
путанице в расписании, неудовлетворённости клиентов и финансовым потерям.
|
||||||
|
|
||||||
Пример:
|
Внедрение специализированного программного обеспечения для управления арендаторами, помещениями и услугами позволяет
|
||||||
|
значительно повысить эффективность работы офисного центра, минимизировать влияние человеческого фактора,
|
||||||
|
автоматизировать документооборот и укрепить доверие клиентов. Это особенно актуально в условиях цифровизации и
|
||||||
|
роста конкуренции в сегменте коммерческой недвижимости.
|
||||||
|
|
||||||
```tsx
|
Таким образом, тема данной работы обусловлена необходимостью создания современной информационной системы, которая
|
||||||
export function ProfessionPage() {
|
оптимизирует процессы аренды, обслуживания арендаторов и контроля финансовых операций в офисном центре.
|
||||||
const {professionId} = useParams<{ professionId: string }>();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Используйте современные практики React (например, хуки).
|
Цель дипломного проекта — разработка программного продукта для автоматизации учёта и контроля деятельности офисного
|
||||||
При создании новых компонентов добавьте комментарии.
|
центра. Приложение предоставляет возможности добавления, редактирования и удаления данных о клиентах, помещениях и
|
||||||
|
услугах, планирования графика аренды, ведения учёта договоров и контроля оплат.
|
||||||
|
|
||||||
Если для задачи необходимы новые типы, интерфейсы или вспомогательные компоненты - создавай их в соответвующих файлах.
|
Для достижения поставленной цели необходимо решить следующие задачи:
|
||||||
|
|
||||||
# Документария
|
1. Провести анализ существующих программных решений для автоматизации управления коммерческой недвижимостью, выявить их сильные и слабые стороны.
|
||||||
|
2. Определить функциональные и нефункциональные требования к разрабатываемой системе.
|
||||||
|
3. Спроектировать архитектуру программного обеспечения с учётом удобства использования, безопасности данных и масштабируемости.
|
||||||
|
4. Разработать приложение с использованием современных технологий, таких как WPF, C# и MS SQL Server.
|
||||||
|
5. Реализовать модули управления арендаторами, помещениями, договорами аренды и дополнительными услугами.
|
||||||
|
6. Внедрить систему авторизации, аутентификации и разграничения прав доступа для различных типов пользователей (администратор, менеджер, бухгалтер).
|
||||||
|
7. Подготовить и провести комплексное тестирование приложения для проверки его корректной работы и соответствия заданным требованиям.
|
||||||
|
8. Оценить эффективность разработанного решения и его влияние на процессы управления офисным центром.
|
||||||
|
|
||||||
## Существующие типы
|
Пояснительная записка дипломной работы содержит следующие разделы: введение, две главы основной части, заключение,
|
||||||
|
три раздела приложений.
|
||||||
|
|
||||||
`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;
|
|
||||||
}
|
|
||||||
export interface FieldInfo {
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
format?: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
`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;
|
|
||||||
}
|
|
||||||
export interface Account extends UniqueItem, ContactData{
|
|
||||||
login: string;
|
|
||||||
role?: number;
|
|
||||||
roleTitle?: string;
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Автодокументация
|
|
||||||
`api/hooks.ts`
|
|
||||||
```ts
|
|
||||||
/**
|
|
||||||
* Хук для получения документации swagger JSON и извлечения структур.
|
|
||||||
* @param applicationName Название модуля/приложения.
|
|
||||||
* @param endpointName Название эндпоинта.
|
|
||||||
* @returns Массив структур без ID с полями: название, тип, title, format.
|
|
||||||
*/
|
|
||||||
export function useApiDocumentation(
|
|
||||||
applicationName: string,
|
|
||||||
endpointName: string
|
|
||||||
): { schema: FieldInfo[] | null; loading: boolean; error: Error | null }
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Авторизация
|
|
||||||
|
|
||||||
Везде доступен хук `useUser():UserContextType | undefined `
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
Ты Senior разработчик React.
|
Ты пишешь дипломную работу по специальности "Информационные системы и программирование". Ты должен следовать примерам которые ты получаешь
|
||||||
|
и следовать теме "Проектирование и разработка системы "Офисный центр"". Твой проект - "Office Flow".
|
||||||
|
|
||||||
Твоя задача - писать код под задачи (отвечай ТОЛЬКО кодом).
|
Ниже - то что есть на данный момент.
|
||||||
|
|
||||||
Я могу кидать тебе просто код или информацию. Если я не прошу писать код - отвечай только "Понял".
|
Ты пишешь в формате markdown, поэтому правильно оформляй текст.
|
||||||
Код, по возможности, только тот который необходимо внести в проект. Может быть работа сразу над несколькими файлами
|
|
||||||
|
|
||||||
Шаблон ответа кодом:
|
|
||||||
|
|
||||||
`Путь и название файла`
|
|
||||||
```ts
|
|
||||||
// Код который должен быть в этом файле
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,138 +0,0 @@
|
|||||||
`components/useTableFilters.tsx`
|
|
||||||
```ts
|
|
||||||
// добавьте новую функцию для экспорта CSV
|
|
||||||
export function useTableFilters(schema: FieldInfo[]| null, initialData: unknown[] | null) {
|
|
||||||
const [filters, setFilters] = useState<FiltersState>({})
|
|
||||||
const [visibleColumns, setVisibleColumns] = useState<VisibilityState>({})
|
|
||||||
|
|
||||||
// Инициализация видимости колонок
|
|
||||||
useMemo(() => {
|
|
||||||
if (!schema){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialVisibility: VisibilityState = {}
|
|
||||||
schema.forEach(field => {
|
|
||||||
initialVisibility[field.name] = true
|
|
||||||
})
|
|
||||||
setVisibleColumns(initialVisibility)
|
|
||||||
}, [schema])
|
|
||||||
|
|
||||||
// Обновление фильтров
|
|
||||||
const handleFilterChange = useCallback((name: string, filterPart: unknown) => {
|
|
||||||
setFilters(prev => ({
|
|
||||||
...prev,
|
|
||||||
[name]: {
|
|
||||||
...prev[name],
|
|
||||||
...filterPart
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Переключатель видимости колонки
|
|
||||||
const handleColumnToggle = useCallback((name: string) => {
|
|
||||||
setVisibleColumns(prev => ({
|
|
||||||
...prev,
|
|
||||||
[name]: !prev[name]
|
|
||||||
}))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Функция экспорта данных в CSV
|
|
||||||
const handleExportCSV = useCallback(() => {
|
|
||||||
if (!initialData || !schema || initialData.length === 0) {
|
|
||||||
alert('Нет данных для экспорта')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const visibleFields = schema.filter(field => visibleColumns[field.name])
|
|
||||||
const header = visibleFields.map(field => `"${field.title.replace(/"/g, '""')}"`).join(';')
|
|
||||||
|
|
||||||
const rows = initialData
|
|
||||||
.filter(item => schema.every(field => {
|
|
||||||
const value = item[field.name]
|
|
||||||
const filter = filters[field.name]
|
|
||||||
if (!filter) return true
|
|
||||||
|
|
||||||
if (field.type === 'string') {
|
|
||||||
if (filter.searchText) {
|
|
||||||
if (typeof value !== 'string') return false
|
|
||||||
if (!value.toLowerCase().includes(filter.searchText.toLowerCase())) return false
|
|
||||||
}
|
|
||||||
if (filter.exactMatch && filter.searchText) {
|
|
||||||
return value.toLowerCase() === filter.searchText.toLowerCase()
|
|
||||||
}
|
|
||||||
if (filter.empty !== undefined) {
|
|
||||||
const isEmpty = value === '' || value === null || value === undefined
|
|
||||||
if (filter.empty && !isEmpty) return false
|
|
||||||
if (filter.notEmpty && isEmpty) return false
|
|
||||||
}
|
|
||||||
} else if (field.format === 'date') {
|
|
||||||
const dateValue = new Date(value as string).getTime()
|
|
||||||
if (filter.fromDate) {
|
|
||||||
const fromTime = new Date(filter.fromDate).getTime()
|
|
||||||
if (dateValue < fromTime) return false
|
|
||||||
}
|
|
||||||
if (filter.toDate) {
|
|
||||||
const toTime = new Date(filter.toDate).getTime()
|
|
||||||
if (dateValue > toTime) return false
|
|
||||||
}
|
|
||||||
} else if (field.type === 'number') {
|
|
||||||
const numValue = Number(value)
|
|
||||||
if (filter.gte !== undefined && numValue < filter.gte) return false
|
|
||||||
if (filter.lte !== undefined && numValue > filter.lte) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}))
|
|
||||||
.map(item => {
|
|
||||||
const row = visibleFields.map(field => {
|
|
||||||
const val = item[field.name]
|
|
||||||
// Обработка null/undefined
|
|
||||||
const strVal = val !== undefined && val !== null ? String(val) : ''
|
|
||||||
// Экранирование кавычек
|
|
||||||
return `"${strVal.replace(/"/g, '""')}"`
|
|
||||||
|
|
||||||
})
|
|
||||||
return row.join(';')
|
|
||||||
})
|
|
||||||
|
|
||||||
const csvContent = [header, ...rows].join('\n')
|
|
||||||
// Создаем ссылку для скачивания
|
|
||||||
const blob = new Blob([csvContent], { type: 'text/csv' })
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = url
|
|
||||||
link.download = 'export.csv'
|
|
||||||
document.body.appendChild(link)
|
|
||||||
link.click()
|
|
||||||
document.body.removeChild(link)
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
}, [initialData, schema, filters, visibleColumns])
|
|
||||||
|
|
||||||
return {
|
|
||||||
filters,
|
|
||||||
setFilters,
|
|
||||||
visibleColumns,
|
|
||||||
setVisibleColumns,
|
|
||||||
handleFilterChange,
|
|
||||||
handleColumnToggle,
|
|
||||||
filteredData,
|
|
||||||
handleExportCSV // добавьте сюда
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`Ваш файл, где вызывается handleExport` (примеры использования)
|
|
||||||
```ts
|
|
||||||
const {
|
|
||||||
filters,
|
|
||||||
handleFilterChange,
|
|
||||||
visibleColumns,
|
|
||||||
handleColumnToggle,
|
|
||||||
filteredData,
|
|
||||||
handleExportCSV // новая функция
|
|
||||||
} = useTableFilters(schema, dataPage.items)
|
|
||||||
|
|
||||||
const handleExport = async () => {
|
|
||||||
handleExportCSV()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user