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

- Обновлены 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": {}, "schematics": {},
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"prefix": "app", "prefix": "ksk-crm",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular/build:application", "builder": "@angular/build:application",
@@ -25,9 +25,7 @@
"input": "public" "input": "public"
} }
], ],
"styles": [ "styles": ["src/styles.css"]
"src/styles.css"
]
}, },
"configurations": { "configurations": {
"production": { "production": {
@@ -48,7 +46,13 @@
"development": { "development": {
"optimization": false, "optimization": false,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true "sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"
@@ -63,6 +67,11 @@
"buildTarget": "kskcrmspa:build:development" "buildTarget": "kskcrmspa:build:development"
} }
}, },
"options": {
"host": "0.0.0.0",
"port": 4200,
"allowedHosts": true
},
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"test": { "test": {
+4 -1
View File
@@ -17,6 +17,9 @@
"@angular/forms": "^22.1.0", "@angular/forms": "^22.1.0",
"@angular/platform-browser": "^22.1.0", "@angular/platform-browser": "^22.1.0",
"@angular/router": "^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", "pocketbase": "^0.28.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0" "tslib": "^2.3.0"
@@ -30,4 +33,4 @@
"typescript": "~6.0.2", "typescript": "~6.0.2",
"vitest": "^4.0.8" "vitest": "^4.0.8"
} }
} }
+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 { provideRouter } from '@angular/router';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideBrowserGlobalErrorListeners(), provideBrowserGlobalErrorListeners(),
provideRouter(routes), provideClientHydration() provideRouter(routes)
] ]
}; };
+10 -14
View File
@@ -2,31 +2,27 @@ import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router'; import { RouterLink, RouterOutlet } from '@angular/router';
@Component({ @Component({
selector: 'app-root', selector: 'ksk-crm-root',
imports: [RouterOutlet, RouterLink], imports: [RouterOutlet],
template: ` template: `
<main> <main>
<a [routerLink]="['/']">На главную</a>&nbsp; <router-outlet />
<section class="content">
<router-outlet />
</section>
</main> </main>
`, `,
styles: ` styles: `
:host { :host {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center;
align-items: center; align-items: center;
padding: 20px; padding: 8px;
min-height: 100vh; min-height: 100dvh;
background: #2e2e2e; background: var(--bg-base);
main { main {
width: 60%; width: 100%;
min-height: 500px; background: var(--surface);
background: white; padding: 1ch;
padding: 20px; border-radius: 5px;
} }
} }
`, `,
+13 -19
View File
@@ -1,31 +1,25 @@
import { ChangeDetectorRef, Component, inject } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { TaskService } from './task-service'; import { TaskService } from './task-service';
import { Task } from './task'; import { Task } from './task';
import { Component, inject, resource, signal } from '@angular/core';
@Component({ @Component({
selector: 'app-task-details', selector: 'ksk-crm-task-details',
imports: [], imports: [RouterLink],
template: ` template: `
<p>{{ task?.name }}</p> <a [routerLink]="['/']">На главную</a>&nbsp;
<p>{{ task?.numberPhone }}</p> <p>{{ taskResource.value()?.name }}</p>
<p>{{ task?.streetAddress }}</p> <p>{{ taskResource.value()?.numberPhone }}</p>
<p>{{ taskResource.value()?.streetAddress }}</p>
`, `,
styles: ``, styles: ``,
}) })
export class TaskDetails { export class TaskDetails {
route: ActivatedRoute = inject(ActivatedRoute); route: ActivatedRoute = inject(ActivatedRoute);
taskId = '';
taskService: TaskService = inject(TaskService); taskService: TaskService = inject(TaskService);
changeDetectorRef = inject(ChangeDetectorRef); taskId = signal(this.route.snapshot.params['id']);
task: Task | undefined; taskResource = resource<Task | null, string>({
constructor() { params: () => this.taskId(),
this.taskId = this.route.snapshot.params['id']; loader: ({ params: id }) => this.taskService.getTaskById(id),
this.taskService.getTaskById(this.taskId).then(task => { });
if (task) {
this.task = task;
this.changeDetectorRef.markForCheck()
}
});
}
} }
+1 -2
View File
@@ -3,14 +3,13 @@ import { Task } from './task';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
@Component({ @Component({
selector: 'app-task-item', selector: 'ksk-crm-task-item',
imports: [ imports: [
RouterLink RouterLink
], ],
template: ` template: `
<p>{{ task().name }}</p> <p>{{ task().name }}</p>
<p>{{ task().city }}, {{ task().streetAddress }}</p> <p>{{ task().city }}, {{ task().streetAddress }}</p>
<p>{{ task().numberPhone }}</p>
<p><a [routerLink]="['/details', task().id]">Подробнее...</a></p> <p><a [routerLink]="['/details', task().id]">Подробнее...</a></p>
`, `,
styles: ` 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 { TaskItem } from './task-item';
import { Task } from './task'; import { Task } from './task';
import { TaskService } from './task-service'; import { TaskService } from './task-service';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideSquarePlus } from '@ng-icons/lucide';
@Component({ @Component({
selector: 'app-home-page', selector: 'ksk-crm-home-page',
imports: [TaskItem, RouterLink, FormsModule], imports: [TaskItem, RouterLink, FormsModule, NgIcon],
providers: [provideIcons({ lucideSquarePlus })],
template: ` template: `
<section> <section>
<a [routerLink]="['/new']">Добавить</a><br>
<input [(ngModel)]="searchInput" type="text" /> <input [(ngModel)]="searchInput" type="text" />
<a routerLink="/new"><ng-icon name="lucideSquarePlus" size="20" /> </a>
</section> </section>
<section class="result"> <section class="result">
@for (task of filteredTasks(); track $index) { @for (task of filteredTasks(); track $index) {
<app-task-item [task]="task" /> <ksk-crm-task-item [task]="task" />
} }
</section> </section>
`, `,
@@ -34,13 +37,23 @@ import { FormsModule } from '@angular/forms';
`, `,
}) })
export class TaskList { export class TaskList {
tasks: Task[] = [];
taskService: TaskService = inject(TaskService); taskService: TaskService = inject(TaskService);
tasks = resource<Task[], null>({
params: signal(null),
loader: () => this.taskService.getAllTasks(),
});
searchInput = signal(''); searchInput = signal('');
filteredTasks = computed<Task[]>(() => { filteredTasks = computed<Task[]>(() => {
const s = this.searchInput(); const s = this.searchInput();
if (s === '') return this.tasks; const tasks = this.tasks.value();
return this.tasks.filter(
if (!tasks) return [];
if (s === '') return tasks;
return tasks.filter(
(task) => (task) =>
task.name.includes(s) || task.name.includes(s) ||
task.city.includes(s) || task.city.includes(s) ||
@@ -48,12 +61,4 @@ export class TaskList {
task.numberPhone.includes(s), 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'; import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-task-new', selector: 'ksk-crm-task-new',
imports: [ imports: [
ReactiveFormsModule ReactiveFormsModule
], ],
+3 -1
View File
@@ -1,10 +1,12 @@
import { Service } from '@angular/core'; import { Service } from '@angular/core';
import { Task } from './task'; import { Task } from './task';
import PocketBase from 'pocketbase' import PocketBase from 'pocketbase'
import { environment } from '../../../environments/environment'
@Service() @Service()
export class TaskService { 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[]> { async getAllTasks(): Promise<Task[]> {
return await this.pbTasks.getFullList(); 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"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<app-root></app-root> <ksk-crm-root></ksk-crm-root>
</body> </body>
</html> </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; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; 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, "isolatedModules": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2023",
"module": "preserve" "module": "preserve"
}, },
"angularCompilerOptions": { "angularCompilerOptions": {