Compare commits

1 Commits
13 changed files with 125 additions and 99 deletions
+47 -33
View File
@@ -1,45 +1,59 @@
# KSK CRM SPA
# Kskcrmspa
Пет-проект: CRM-система для управления заказами частного мастера.
Проект создаётся в практических целях для глубинного освоения **Angular 22** и современной экосистемы фронтенд-разработки.
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 22.1.5.
## Стек технологий
## Development server
* **Frontend:** Angular 22
* **Backend / Database:** PocketBase
* **Package Manager:** pnpm
To start a local development server, run:
## Локальный запуск проекта
Проект состоит из двух частей: легковесного бэкенда (PocketBase) и фронтенда (Angular 22).
### 1. Запуск бэкенда (PocketBase)
В репозитории уже лежат файлы структуры базы данных в папке `pb_migrations`. При запуске PocketBase автоматически накатит нужные миграции.
1. Скачайте исполняемый файл **PocketBase** для вашей ОС с [официального сайта](https://pocketbase.io/docs/) (или разверните через Docker).
2. Поместите бинарник `pocketbase` в корневую папку этого проекта (на один уровень с папкой `pb_migrations`).
3. Запустите сервер:
```bash
./pocketbase serve
ng serve
```
*PocketBase запустится на `http://127.0.0.1:8090` и автоматически применит схему БД.*
### 2. Настройка и запуск фронтенда (Angular)
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
1. Установите зависимости проекта:
```bash
pnpm install
ng generate component component-name
```
2. Убедитесь, что адрес API указан верно. Конфигурация для локальной разработки находится в файле `src/environments/environment.development.ts`:
```typescript
export const environment = {
production: false,
apiUrl: 'http://127.0.0.1:8090'
};
```
3. Запустите dev-сервер:
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
pnpm start
ng generate --help
```
4. Откройте приложение в браузере по адресу `http://localhost:4200`.
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
+4 -1
View File
@@ -4,5 +4,8 @@ import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners(), provideRouter(routes)],
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes)
]
};
+9 -3
View File
@@ -2,21 +2,27 @@ import { Routes } from '@angular/router';
import { TaskList } from './features/tasks/task-list';
import { TaskDetails } from './features/tasks/task-details';
import { TaskNew } from './features/tasks/task-new';
import { Dev } from './features/dev';
export const routes: Routes = [
{
path: '',
component: TaskList,
title: 'Главная страница',
title: 'Главная страница'
},
{
path: 'details/:id',
component: TaskDetails,
title: 'Подробно',
title: 'Подробно'
},
{
path: 'new',
component: TaskNew,
title: 'Новая',
title: 'Новая'
},
{
path: 'dev',
component: Dev,
title: 'Dev'
}
];
+1 -2
View File
@@ -21,9 +21,8 @@ import { RouterOutlet } from '@angular/router';
main {
width: 100%;
background: var(--color-surface);
padding: 0.5rem;
padding: 1rem;
border-radius: 5px;
flex-grow: 1;
}
}
`,
+27
View File
@@ -0,0 +1,27 @@
import { Component, signal } from '@angular/core';
@Component({
imports: [],
selector: 'ksk-crm-dev',
styles: `
.outlined {
border: 1px solid var(--color-primary);
}
.rounded {
border-radius: 3px;
}
`,
template: `
<p [class.outlined]="isOutlined()" >dev works!</p>
<button (click)="handleAction()">Action</button>
`,
})
export class Dev {
outlined = signal<string[]>(["rounded"])
isOutlined = signal(false);
handleAction() {
this.isOutlined.set(true);
}
}
+5 -17
View File
@@ -4,30 +4,18 @@ import { RouterLink } from '@angular/router';
@Component({
selector: 'ksk-crm-task-item',
imports: [RouterLink],
imports: [
RouterLink
],
template: `
<a [routerLink]="['/details', task().id]">
<p>{{ task().name }}</p>
<p>{{ task().city }}, {{ task().streetAddress }}</p>
</a>
<p><a [routerLink]="['/details', task().id]">Подробнее...</a></p>
`,
styles: `
:host {
display: block;
padding: 1rem;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: color-mix(in oklch, var(--color-primary) 25%, transparent);
}
&:active {
background: color-mix(in oklch, var(--color-primary) 25%, transparent);
}
}
a {
color: var(--color-gray);
margin-bottom: 20px;
}
`,
})
+4 -18
View File
@@ -24,19 +24,15 @@ import { lucideCircleX, lucideSquarePlus } from '@ng-icons/lucide';
</a>
</section>
<section class="result">
@for (task of filteredTasks(); track task.id) {
@for (task of filteredTasks(); track $index) {
<ksk-crm-task-item [task]="task" />
@if (!$last) {
<hr />
}
} @empty {
<p>Список пуст</p>
}
</section>
`,
styles: `
.topBar {
display: flex;
padding-top: 1rem;
.input-wrapper {
display: inline-flex;
@@ -68,23 +64,12 @@ import { lucideCircleX, lucideSquarePlus } from '@ng-icons/lucide';
display: inline-flex;
align-items: center;
justify-content: center;
margin-left: 0.5em;
margin-left: .5em;
}
}
.result {
margin-top: 20px;
display: flex;
flex-direction: column;
> p {
text-align: center;
}
}
hr {
border: 1px solid var(--color-primary);
opacity: 0.3;
}
`,
})
@@ -92,6 +77,7 @@ export class TaskList {
taskService: TaskService = inject(TaskService);
tasks = resource<Task[], null>({
params: signal(null),
loader: () => this.taskService.getAllTasks(),
});
+13 -11
View File
@@ -5,24 +5,26 @@ import { Router } from '@angular/router';
@Component({
selector: 'ksk-crm-task-new',
imports: [ReactiveFormsModule],
imports: [
ReactiveFormsModule
],
template: `
<form [formGroup]="formGroup" (submit)="submitForm()">
<div class="form-field">
<label for="name">Имя: </label>
<input id="name" type="text" formControlName="name" />
<input id="name" type="text" formControlName="name">
</div>
<div class="form-field">
<label for="city">Город: </label>
<input id="city" type="text" formControlName="city" />
<input id="city" type="text" formControlName="city">
</div>
<div class="form-field">
<label for="streetAddress">Адрес: </label>
<input id="streetAddress" type="text" formControlName="streetAddress" />
<input id="streetAddress" type="text" formControlName="streetAddress">
</div>
<div class="form-field">
<label for="numberPhone">Номер телефона: </label>
<input id="numberPhone" type="text" formControlName="numberPhone" />
<input id="numberPhone" type="text" formControlName="numberPhone">
</div>
<button type="submit">Добавить</button>
</form>
@@ -50,15 +52,15 @@ export class TaskNew {
city: new FormControl(''),
streetAddress: new FormControl(''),
numberPhone: new FormControl(''),
});
})
async submitForm(){
const createdTask = await this.taskService.createTask({
name: this.formGroup.value.name ?? '',
city: this.formGroup.value.city ?? '',
streetAddress: this.formGroup.value.streetAddress ?? '',
numberPhone: this.formGroup.value.numberPhone ?? '',
});
name: this.formGroup.value.name ?? "",
city: this.formGroup.value.city ?? "",
streetAddress: this.formGroup.value.streetAddress ?? "",
numberPhone: this.formGroup.value.numberPhone ?? "",
})
this.formGroup.reset();
await this.router.navigate(['details', createdTask.id]);
+2 -2
View File
@@ -1,7 +1,7 @@
import { Service } from '@angular/core';
import { Task } from './task';
import PocketBase from 'pocketbase';
import { environment } from '../../../environments/environment';
import PocketBase from 'pocketbase'
import { environment } from '../../../environments/environment'
@Service()
export class TaskService {
+1 -1
View File
@@ -1,4 +1,4 @@
export const environment = {
production: false,
apiUrl: 'http://localhost:8090',
apiUrl: "http://10.1.1.40:8090",
};
+1 -1
View File
@@ -1,4 +1,4 @@
export const environment = {
production: true,
apiUrl: 'http://localhost:8090',
apiUrl: 'http://10.1.1.40:8090',
};
+2 -1
View File
@@ -2,4 +2,5 @@ import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));