Многосторнние изменения:

- Обновлены Angular и зависимости до последней версии (22.1.5, postcss, zod и т.д.)
- Заменена загрузка данных в конструкторе на API resource() в task-list и task-details
- Переименовать селекторы компонентов из app-* в ksk-crm-*
- Добавлены конфиги окружения с настраиваемым API URL
- Подключены @ng-icons/lucide для иконок
- Добавлен шрифт Inter и CSS-переменные для темизации
- Обновлён target tsconfig до ES2023 (для доступности методов в js)
This commit is contained in:
2026-09-05 12:52:01 +03:00
parent 435ae2e594
commit 8383877797
16 changed files with 394 additions and 308 deletions
+14 -5
View File
@@ -12,7 +12,7 @@
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"prefix": "ksk-crm",
"architect": {
"build": {
"builder": "@angular/build:application",
@@ -25,9 +25,7 @@
"input": "public"
}
],
"styles": [
"src/styles.css"
]
"styles": ["src/styles.css"]
},
"configurations": {
"production": {
@@ -48,7 +46,13 @@
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
@@ -63,6 +67,11 @@
"buildTarget": "kskcrmspa:build:development"
}
},
"options": {
"host": "0.0.0.0",
"port": 4200,
"allowedHosts": true
},
"defaultConfiguration": "development"
},
"test": {
+3
View File
@@ -17,6 +17,9 @@
"@angular/forms": "^22.1.0",
"@angular/platform-browser": "^22.1.0",
"@angular/router": "^22.1.0",
"@fontsource/inter": "^5.3.0",
"@ng-icons/core": "^36.0.0",
"@ng-icons/lucide": "^36.0.0",
"pocketbase": "^0.28.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
+292 -245
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
allowBuilds:
'@parcel/watcher': true
esbuild: true
lmdb: true
msgpackr-extract: true
+1 -2
View File
@@ -2,11 +2,10 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes), provideClientHydration()
provideRouter(routes)
]
};
+9 -13
View File
@@ -2,31 +2,27 @@ import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet, RouterLink],
selector: 'ksk-crm-root',
imports: [RouterOutlet],
template: `
<main>
<a [routerLink]="['/']">На главную</a>&nbsp;
<section class="content">
<router-outlet />
</section>
</main>
`,
styles: `
:host {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
min-height: 100vh;
background: #2e2e2e;
padding: 8px;
min-height: 100dvh;
background: var(--bg-base);
main {
width: 60%;
min-height: 500px;
background: white;
padding: 20px;
width: 100%;
background: var(--surface);
padding: 1ch;
border-radius: 5px;
}
}
`,
+12 -18
View File
@@ -1,31 +1,25 @@
import { ChangeDetectorRef, Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { TaskService } from './task-service';
import { Task } from './task';
import { Component, inject, resource, signal } from '@angular/core';
@Component({
selector: 'app-task-details',
imports: [],
selector: 'ksk-crm-task-details',
imports: [RouterLink],
template: `
<p>{{ task?.name }}</p>
<p>{{ task?.numberPhone }}</p>
<p>{{ task?.streetAddress }}</p>
<a [routerLink]="['/']">На главную</a>&nbsp;
<p>{{ taskResource.value()?.name }}</p>
<p>{{ taskResource.value()?.numberPhone }}</p>
<p>{{ taskResource.value()?.streetAddress }}</p>
`,
styles: ``,
})
export class TaskDetails {
route: ActivatedRoute = inject(ActivatedRoute);
taskId = '';
taskService: TaskService = inject(TaskService);
changeDetectorRef = inject(ChangeDetectorRef);
task: Task | undefined;
constructor() {
this.taskId = this.route.snapshot.params['id'];
this.taskService.getTaskById(this.taskId).then(task => {
if (task) {
this.task = task;
this.changeDetectorRef.markForCheck()
}
taskId = signal(this.route.snapshot.params['id']);
taskResource = resource<Task | null, string>({
params: () => this.taskId(),
loader: ({ params: id }) => this.taskService.getTaskById(id),
});
}
}
+1 -2
View File
@@ -3,14 +3,13 @@ import { Task } from './task';
import { RouterLink } from '@angular/router';
@Component({
selector: 'app-task-item',
selector: 'ksk-crm-task-item',
imports: [
RouterLink
],
template: `
<p>{{ task().name }}</p>
<p>{{ task().city }}, {{ task().streetAddress }}</p>
<p>{{ task().numberPhone }}</p>
<p><a [routerLink]="['/details', task().id]">Подробнее...</a></p>
`,
styles: `
+21 -16
View File
@@ -1,21 +1,24 @@
import { Component, computed, inject, signal } from '@angular/core';
import { Component, computed, inject, linkedSignal, resource, signal } from '@angular/core';
import { TaskItem } from './task-item';
import { Task } from './task';
import { TaskService } from './task-service';
import { RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideSquarePlus } from '@ng-icons/lucide';
@Component({
selector: 'app-home-page',
imports: [TaskItem, RouterLink, FormsModule],
selector: 'ksk-crm-home-page',
imports: [TaskItem, RouterLink, FormsModule, NgIcon],
providers: [provideIcons({ lucideSquarePlus })],
template: `
<section>
<a [routerLink]="['/new']">Добавить</a><br>
<input [(ngModel)]="searchInput" type="text" />
<a routerLink="/new"><ng-icon name="lucideSquarePlus" size="20" /> </a>
</section>
<section class="result">
@for (task of filteredTasks(); track $index) {
<app-task-item [task]="task" />
<ksk-crm-task-item [task]="task" />
}
</section>
`,
@@ -34,13 +37,23 @@ import { FormsModule } from '@angular/forms';
`,
})
export class TaskList {
tasks: Task[] = [];
taskService: TaskService = inject(TaskService);
tasks = resource<Task[], null>({
params: signal(null),
loader: () => this.taskService.getAllTasks(),
});
searchInput = signal('');
filteredTasks = computed<Task[]>(() => {
const s = this.searchInput();
if (s === '') return this.tasks;
return this.tasks.filter(
const tasks = this.tasks.value();
if (!tasks) return [];
if (s === '') return tasks;
return tasks.filter(
(task) =>
task.name.includes(s) ||
task.city.includes(s) ||
@@ -48,12 +61,4 @@ export class TaskList {
task.numberPhone.includes(s),
);
});
constructor() {
this.taskService.getAllTasks().then(tasks => {
this.tasks = tasks;
this.searchInput.update(_ => " ")
this.searchInput.update(_ => '');
});
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
@Component({
selector: 'app-task-new',
selector: 'ksk-crm-task-new',
imports: [
ReactiveFormsModule
],
+3 -1
View File
@@ -1,10 +1,12 @@
import { Service } from '@angular/core';
import { Task } from './task';
import PocketBase from 'pocketbase'
import { environment } from '../../../environments/environment'
@Service()
export class TaskService {
pbTasks = new PocketBase('http://127.0.0.1:8090').collection<Task>('tasks');
private apiUrl = environment.apiUrl;
private pbTasks = new PocketBase(this.apiUrl).collection<Task>('tasks');
async getAllTasks(): Promise<Task[]> {
return await this.pbTasks.getFullList();
@@ -0,0 +1,4 @@
export const environment = {
production: false,
apiUrl: "http://192.168.1.22:8090",
};
+4
View File
@@ -0,0 +1,4 @@
export const environment = {
production: true,
apiUrl: 'http://192.168.1.22:8090',
};
+1 -1
View File
@@ -9,6 +9,6 @@
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
<ksk-crm-root></ksk-crm-root>
</body>
</html>
+19
View File
@@ -1,5 +1,24 @@
@import '@fontsource/inter/400.css';
@import '@fontsource/inter/500.css';
@import '@fontsource/inter/600.css';
@import '@fontsource/inter/700.css';
:root {
--gray: oklch(28% 0 0);
--white: white;
--font-family-base: 'Inter';
--bg-base: var(--gray);
--surface: var(--white);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family-base), system-ui, -apple-system, sans-serif;
}
+1 -1
View File
@@ -11,7 +11,7 @@
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"target": "ES2023",
"module": "preserve"
},
"angularCompilerOptions": {