diff --git a/.gitignore b/.gitignore
index 854acd5..de397bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,3 +42,6 @@ __screenshots__/
# System files
.DS_Store
Thumbs.db
+
+
+pb_data/
diff --git a/angular.json b/angular.json
index 4c468be..5f96c3e 100644
--- a/angular.json
+++ b/angular.json
@@ -2,7 +2,8 @@
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
- "packageManager": "pnpm"
+ "packageManager": "pnpm",
+ "analytics": false
},
"newProjectRoot": "projects",
"projects": {
diff --git a/package.json b/package.json
index 9ea3e6b..afef125 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@angular/forms": "^22.1.0",
"@angular/platform-browser": "^22.1.0",
"@angular/router": "^22.1.0",
+ "pocketbase": "^0.28.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
diff --git a/pb_migrations/1787844565_created_tasks.js b/pb_migrations/1787844565_created_tasks.js
new file mode 100644
index 0000000..5b16aa1
--- /dev/null
+++ b/pb_migrations/1787844565_created_tasks.js
@@ -0,0 +1,120 @@
+///
+migrate((app) => {
+ const collection = new Collection({
+ "createRule": null,
+ "deleteRule": null,
+ "fields": [
+ {
+ "autogeneratePattern": "[a-z0-9]{15}",
+ "help": "",
+ "hidden": false,
+ "id": "text3208210256",
+ "max": 15,
+ "min": 15,
+ "name": "id",
+ "pattern": "^[a-z0-9]+$",
+ "presentable": false,
+ "primaryKey": true,
+ "required": true,
+ "system": true,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text1579384326",
+ "max": 0,
+ "min": 0,
+ "name": "name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text760939060",
+ "max": 0,
+ "min": 0,
+ "name": "city",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text3757513383",
+ "max": 0,
+ "min": 0,
+ "name": "streetAddress",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "help": "",
+ "hidden": false,
+ "id": "text2590966647",
+ "max": 0,
+ "min": 0,
+ "name": "numberPhone",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "hidden": false,
+ "id": "autodate2990389176",
+ "name": "created",
+ "onCreate": true,
+ "onUpdate": false,
+ "presentable": false,
+ "system": false,
+ "type": "autodate"
+ },
+ {
+ "hidden": false,
+ "id": "autodate3332085495",
+ "name": "updated",
+ "onCreate": true,
+ "onUpdate": true,
+ "presentable": false,
+ "system": false,
+ "type": "autodate"
+ }
+ ],
+ "id": "pbc_2602490748",
+ "indexes": [
+ "CREATE UNIQUE INDEX `idx_gxh7schxgw` ON `tasks` (`numberPhone`)"
+ ],
+ "listRule": null,
+ "name": "tasks",
+ "system": false,
+ "type": "base",
+ "updateRule": null,
+ "viewRule": null
+ });
+
+ return app.save(collection);
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_2602490748");
+
+ return app.delete(collection);
+})
diff --git a/pb_migrations/1787845008_updated_tasks.js b/pb_migrations/1787845008_updated_tasks.js
new file mode 100644
index 0000000..97a7607
--- /dev/null
+++ b/pb_migrations/1787845008_updated_tasks.js
@@ -0,0 +1,20 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_2602490748")
+
+ // update collection data
+ unmarshal({
+ "listRule": ""
+ }, collection)
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_2602490748")
+
+ // update collection data
+ unmarshal({
+ "listRule": null
+ }, collection)
+
+ return app.save(collection)
+})
diff --git a/pb_migrations/1787845090_updated_tasks.js b/pb_migrations/1787845090_updated_tasks.js
new file mode 100644
index 0000000..8c9b7bb
--- /dev/null
+++ b/pb_migrations/1787845090_updated_tasks.js
@@ -0,0 +1,26 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_2602490748")
+
+ // update collection data
+ unmarshal({
+ "createRule": "",
+ "deleteRule": "",
+ "updateRule": "",
+ "viewRule": ""
+ }, collection)
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_2602490748")
+
+ // update collection data
+ unmarshal({
+ "createRule": null,
+ "deleteRule": null,
+ "updateRule": null,
+ "viewRule": null
+ }, collection)
+
+ return app.save(collection)
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b92fa9e..9df7396 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@angular/router':
specifier: ^22.1.0
version: 22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(@angular/platform-browser@22.1.3(@angular/common@22.1.3(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2))(rxjs@7.8.2))(@angular/core@22.1.3(@angular/compiler@22.1.3)(rxjs@7.8.2)))(rxjs@7.8.2)
+ pocketbase:
+ specifier: ^0.28.0
+ version: 0.28.0
rxjs:
specifier: ~7.8.0
version: 7.8.2
@@ -2101,6 +2104,9 @@ packages:
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
engines: {node: '>=16.20.0'}
+ pocketbase@0.28.0:
+ resolution: {integrity: sha512-aCrab5XPPQ40iKIKicQLdUvrAvJQCHZ3cwZD5i22A8PVM/EcemUta7WFw0x4G6WQEOBMa/8kioqCR62ozk4KvQ==}
+
postcss-media-query-parser@0.2.3:
resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
@@ -4297,6 +4303,8 @@ snapshots:
pkce-challenge@5.0.1: {}
+ pocketbase@0.28.0: {}
+
postcss-media-query-parser@0.2.3: {}
postcss-safe-parser@7.0.1(postcss@8.5.26):
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index e60fc79..0f4fd64 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,10 +1,12 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
+
import { routes } from './app.routes';
+import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
- provideRouter(routes)
+ provideRouter(routes), provideClientHydration()
]
};
diff --git a/src/app/app.css b/src/app/app.css
deleted file mode 100644
index e69de29..0000000
diff --git a/src/app/app.html b/src/app/app.html
deleted file mode 100644
index a1c4296..0000000
--- a/src/app/app.html
+++ /dev/null
@@ -1,344 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hello, {{ title() }}
-
Congratulations! Your app is running. 🎉
-
-
-
-
- @for (item of [
- { title: 'Explore the Docs', link: 'https://angular.dev' },
- { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
- { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'},
- { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
- { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
- { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
- ]; track item.title) {
-
- {{ item.title }}
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index dc39edb..5ecb554 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,3 +1,22 @@
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';
-export const routes: Routes = [];
+export const routes: Routes = [
+ {
+ path: '',
+ component: TaskList,
+ title: 'Главная страница'
+ },
+ {
+ path: 'details/:id',
+ component: TaskDetails,
+ title: 'Подробно'
+ },
+ {
+ path: 'new',
+ component: TaskNew,
+ title: 'Новая'
+ }
+];
diff --git a/src/app/app.spec.ts b/src/app/app.spec.ts
deleted file mode 100644
index 844d97d..0000000
--- a/src/app/app.spec.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { App } from './app';
-
-describe('App', () => {
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [App],
- })
- .compileComponents();
- });
-
- it('should create the app', () => {
- const fixture = TestBed.createComponent(App);
- const app = fixture.componentInstance;
- expect(app).toBeTruthy();
- });
-
- it('should render title', async () => {
- const fixture = TestBed.createComponent(App);
- await fixture.whenStable();
- const compiled = fixture.nativeElement as HTMLElement;
- expect(compiled.querySelector('h1')?.textContent).toContain('Hello, kskcrmspa');
- });
-});
diff --git a/src/app/app.ts b/src/app/app.ts
index 1837821..7b3b76b 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -1,12 +1,34 @@
-import { Component, signal } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { Component } from '@angular/core';
+import { RouterLink, RouterOutlet } from '@angular/router';
@Component({
- imports: [RouterOutlet],
selector: 'app-root',
- styleUrl: './app.css',
- templateUrl: './app.html',
+ imports: [RouterOutlet, RouterLink],
+ template: `
+
+ На главную
+
+
+ `,
+ styles: `
+ :host {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding: 20px;
+ min-height: 100vh;
+ background: #2e2e2e;
+
+ main {
+ width: 60%;
+ min-height: 500px;
+ background: white;
+ padding: 20px;
+ }
+ }
+ `,
})
-export class App {
- protected readonly title = signal('kskcrmspa');
-}
+export class App {}
diff --git a/src/app/features/tasks/task-details.ts b/src/app/features/tasks/task-details.ts
new file mode 100644
index 0000000..17ecf23
--- /dev/null
+++ b/src/app/features/tasks/task-details.ts
@@ -0,0 +1,31 @@
+import { ChangeDetectorRef, Component, inject } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { TaskService } from './task-service';
+import { Task } from './task';
+
+@Component({
+ selector: 'app-task-details',
+ imports: [],
+ template: `
+
{{ task?.name }}
+ {{ task?.numberPhone }}
+ {{ task?.streetAddress }}
+ `,
+ 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()
+ }
+ });
+ }
+}
diff --git a/src/app/features/tasks/task-item.ts b/src/app/features/tasks/task-item.ts
new file mode 100644
index 0000000..d5028a7
--- /dev/null
+++ b/src/app/features/tasks/task-item.ts
@@ -0,0 +1,25 @@
+import { Component, input } from '@angular/core';
+import { Task } from './task';
+import { RouterLink } from '@angular/router';
+
+@Component({
+ selector: 'app-task-item',
+ imports: [
+ RouterLink
+ ],
+ template: `
+ {{ task().name }}
+ {{ task().city }}, {{ task().streetAddress }}
+ {{ task().numberPhone }}
+ Подробнее...
+ `,
+ styles: `
+ :host {
+ display: block;
+ margin-bottom: 20px;
+ }
+ `,
+})
+export class TaskItem {
+ task = input.required();
+}
diff --git a/src/app/features/tasks/task-list.ts b/src/app/features/tasks/task-list.ts
new file mode 100644
index 0000000..51dca5a
--- /dev/null
+++ b/src/app/features/tasks/task-list.ts
@@ -0,0 +1,59 @@
+import { Component, computed, inject, 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';
+
+@Component({
+ selector: 'app-home-page',
+ imports: [TaskItem, RouterLink, FormsModule],
+ template: `
+
+
+ @for (task of filteredTasks(); track $index) {
+
+ }
+
+ `,
+ styles: `
+ input {
+ outline: none;
+ padding: 5px;
+ }
+ button {
+ cursor: pointer;
+ padding: 5px;
+ }
+ .result {
+ margin-top: 20px;
+ }
+ `,
+})
+export class TaskList {
+ tasks: Task[] = [];
+ taskService: TaskService = inject(TaskService);
+ searchInput = signal('');
+ filteredTasks = computed(() => {
+ const s = this.searchInput();
+ if (s === '') return this.tasks;
+ return this.tasks.filter(
+ (task) =>
+ task.name.includes(s) ||
+ task.city.includes(s) ||
+ task.streetAddress.includes(s) ||
+ task.numberPhone.includes(s),
+ );
+ });
+
+ constructor() {
+ this.taskService.getAllTasks().then(tasks => {
+ this.tasks = tasks;
+ this.searchInput.update(_ => " ")
+ this.searchInput.update(_ => '');
+ });
+ }
+}
diff --git a/src/app/features/tasks/task-new.ts b/src/app/features/tasks/task-new.ts
new file mode 100644
index 0000000..5f4596d
--- /dev/null
+++ b/src/app/features/tasks/task-new.ts
@@ -0,0 +1,68 @@
+import { Component, inject } from '@angular/core';
+import { TaskService } from './task-service';
+import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+
+@Component({
+ selector: 'app-task-new',
+ imports: [
+ ReactiveFormsModule
+ ],
+ template: `
+
+ `,
+ styles: `
+ form{
+ max-width: 400px;
+ margin: 0 auto;
+ text-align: right;
+ }
+ .form-field {
+ margin-bottom: 10px;
+ }
+ button{
+ padding: 5px;
+ }
+ `,
+})
+export class TaskNew {
+ taskService: TaskService = inject(TaskService);
+ router: Router = inject(Router);
+
+ formGroup = new FormGroup({
+ name: new FormControl(''),
+ 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 ?? "",
+ })
+
+ this.formGroup.reset();
+ await this.router.navigate(['details', createdTask.id]);
+ }
+}
diff --git a/src/app/features/tasks/task-service.ts b/src/app/features/tasks/task-service.ts
new file mode 100644
index 0000000..f29a63d
--- /dev/null
+++ b/src/app/features/tasks/task-service.ts
@@ -0,0 +1,20 @@
+import { Service } from '@angular/core';
+import { Task } from './task';
+import PocketBase from 'pocketbase'
+
+@Service()
+export class TaskService {
+ pbTasks = new PocketBase('http://127.0.0.1:8090').collection('tasks');
+
+ async getAllTasks(): Promise {
+ return await this.pbTasks.getFullList();
+ }
+
+ async getTaskById(id: string): Promise {
+ return await this.pbTasks.getOne(id);
+ }
+
+ async createTask(task: Omit): Promise {
+ return await this.pbTasks.create(task);
+ }
+}
diff --git a/src/app/features/tasks/task.ts b/src/app/features/tasks/task.ts
new file mode 100644
index 0000000..ae133db
--- /dev/null
+++ b/src/app/features/tasks/task.ts
@@ -0,0 +1,7 @@
+export interface Task {
+ id: string;
+ name: string;
+ city: string;
+ streetAddress: string;
+ numberPhone: string;
+}
diff --git a/src/index.html b/src/index.html
index 3026b74..977bb18 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,10 +1,11 @@
-
+
- Kskcrmspa
+ KskcrmSPA
+
diff --git a/src/styles.css b/src/styles.css
index 90d4ee0..cca57b3 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1 +1,5 @@
-/* You can add global styles to this file, and also import other style files */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}