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; +}