Commit 945c4ad3 authored by tinhbe's avatar tinhbe

update done JobManagement Admin

parent 1d05da0f
<div className="tw-container tw-content-center">
<div class="tw-flex tw-justify-end tw-pb-5">
<button nz-button nzType="primary" (click)="addJob()">+ Thêm công việc mới</button>
</div>
<nz-table
#JobTable
[nzData]="listOfJob"
[nzLoading]="loading"
[nzFrontPagination]="true"
nzShowSizeChanger
[nzPageSize]="5"
>
<thead>
<tr>
<th>STT</th>
<th>Loại công việc</th>
<th>Tiêu đề</th>
<th>Công ty</th>
<th>Địa chỉ</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody>
@for (data of JobTable.data; track data) {
<tr>
<td>{{ listOfJob.indexOf(data) + 1 }}</td>
<td>{{ data.type }}</td>
<td>{{ data.title }}</td>
<td>{{ data.company }}</td>
<td>{{ data.location }}</td>
<td>
<nz-space>
<button class="tw-mr-2" nz-button nzType="primary" nzSize="small" (click)="JobDetail(data.id)">
Xem chi tiết
</button>
<button
class="tw-mr-2"
nz-button
nzType="default"
nzSize="small"
(click)="onEdit(data)"
>
Sửa
</button>
<button nz-button nzType="dashed" nzSize="small" nzDanger (click)="showDeleteConfirm(data.id)">
Xóa
</button>
</nz-space>
</td>
</tr>
}
</tbody>
</nz-table>
<nz-modal [(nzVisible)]="isOpenModal" [nzTitle]="isOnEdit ? 'Sa công vic' : 'Chi tiết công vic'" (nzOnOk)="onOk()" (nzOnCancel)="onCancel()">
<ng-container *nzModalContent>
<div *ngIf="selectedJob">
<form nz-form [formGroup]="validateJobForm">
<nz-form-item>
<nz-form-label [nzSpan]="7">Loại công việc</nz-form-label>
<nz-form-control [nzSpan]="17">
<nz-select formControlName="type" placeholder="Loại công việc" ngModel={{selectedJob.type}}>
<nz-option nzValue="Full Time" nzLabel="Full Time"></nz-option>
<nz-option nzValue="Part Time" nzLabel="Part Time"></nz-option>
<nz-option nzValue="Freelance" nzLabel="Freelance"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">Tên công việc</nz-form-label>
<nz-form-control [nzSpan]="17" >
<input class="tw-w-full tw-p-2 tw-bg-white tw-border tw-rounded-md focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500 focus:tw-border-blue-500" nz-input formControlName="location" nz-input formControlName="title" placeholder="Tên công việc" [ngModel]="selectedJob.title"/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">Công ty</nz-form-label>
<nz-form-control [nzSpan]="17">
<input class="tw-w-full tw-p-2 tw-bg-white tw-border tw-rounded-md focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500 focus:tw-border-blue-500" nz-input formControlName="location" nz-input formControlName="company" placeholder="Công ty" [ngModel]=" selectedJob.company"/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">Địa chỉ</nz-form-label>
<nz-form-control [nzSpan]="17">
<input class="tw-w-full tw-p-2 tw-bg-white tw-border tw-rounded-md focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500 focus:tw-border-blue-500" nz-input formControlName="location" placeholder="Địa chỉ" [ngModel]="selectedJob.location" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">Mô tả</nz-form-label>
<nz-form-control [nzSpan]="17">
<textarea class="tw-w-full tw-h-40 tw-p-2 tw-bg-white tw-border tw-rounded-md focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500 focus:tw-border-blue-500" nz-input formControlName="description" placeholder="Mô tả công việc" [ngModel]="selectedJob.description"></textarea>
</nz-form-control>
</nz-form-item>
</form>
</div>
</ng-container>
</nz-modal>
</div>
//#region imports
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { JobService } from "../../data-access/services/JobService.service";
import { NzTableModule, NzTableQueryParams } from 'ng-zorro-antd/table';
import { catchError, finalize, of, tap } from "rxjs";
import { JobModel } from "../../data-access/model/jobModel.model";
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzButtonModule } from "ng-zorro-antd/button";
import { NzSpaceModule } from 'ng-zorro-antd/space';
import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { NzFormModule } from "ng-zorro-antd/form";
import { NzSelectModule } from 'ng-zorro-antd/select';
//#endregion
//#region Component
@Component({
selector: "meu-admin-job",
templateUrl: "./Job.component.html",
standalone: true,
imports: [CommonModule,
NzTableModule,
NzDividerModule,
NzButtonModule,
NzSpaceModule,
NzModalModule,
NzModalModule,
NzFormModule,
ReactiveFormsModule,
NzSelectModule
],
})
//#endregion
export class JobComponent implements OnInit {
//#region Variable
selectedJob?: JobModel;
listOfJob: JobModel[] = [];
loading = true;
message = "";
//modal
isOpenModal = false;
isOnEdit = false;
isOnAdd = false;
validateJobForm = this.fb.group({
type: this.fb.control('', [Validators.required]),
create_at: this.fb.control('', [Validators.required]),
company_url: this.fb.control('', [Validators.required]),
company: this.fb.control('', [Validators.required]),
location: this.fb.control('', [Validators.required]),
title: this.fb.control('', [Validators.required]),
description: this.fb.control('', [Validators.required]),
});
//#endregion
//#region LoadData
constructor(private fb: FormBuilder,private jobService: JobService, private modal: NzModalService) {}
loadDataFromServer(): void {
this.loading = true;
this.jobService.jobsGet().pipe(
tap((res) => {
this.listOfJob = res.responseData.items;
}),
catchError((err) => {
console.log(err);
return of(null);
}),
finalize(() => (this.loading = false))
).subscribe();
}
ngOnInit(): void {
this.loadDataFromServer();
}
//#endregion
//#region Handler
resetForm()
{
this.validateJobForm.reset();
this.selectedJob = {} as JobModel;
}
EnableForm()
{
this.validateJobForm.enable();
}
DisableForm()
{
this.validateJobForm.disable();
}
onOk(): void {
console.log(this.onEdit, this.selectedJob);
if (this.isOnEdit && this.selectedJob) {
const updatedJob: JobModel = {
id: this.selectedJob.id,
type: this.validateJobForm.value.type ?? undefined,
created_at: this.selectedJob.created_at,
company: this.validateJobForm.value.company ?? undefined,
company_url: this.validateJobForm.value.company_url ?? undefined,
location: this.validateJobForm.value.location ?? undefined,
title: this.validateJobForm.value.title ?? undefined,
description: this.validateJobForm.value.description ?? undefined,
};
this.loading = true;
this.jobService.editJob(updatedJob).pipe(
tap((res) => {
this.message = res.message;
this.loadDataFromServer();
}),
catchError((err) => {
console.error(err);
return of(null);
})
).subscribe();
}else if(this.isOnAdd && this.selectedJob)
{
const newJob: JobModel = {
id: this.selectedJob.id,
type: this.validateJobForm.value.type ?? undefined,
created_at: this.selectedJob.created_at,
company: this.validateJobForm.value.company ?? undefined,
company_url: this.validateJobForm.value.company_url ?? undefined,
location: this.validateJobForm.value.location ?? undefined,
title: this.validateJobForm.value.title ?? undefined,
description: this.validateJobForm.value.description ?? undefined,
};
this.loading = true;
this.jobService.addingNewJob(newJob).pipe(
tap((res) => {
this.message = res.message;
this.loadDataFromServer();
}),
catchError((err) => {
console.error(err);
return of(null);
})
).subscribe();
}
this.isOpenModal = false;
this.isOnEdit = false;
this.isOnAdd = false;
}
onCancel(): void {
this.isOpenModal = false;
this.isOnEdit = false;
}
//#endregion
//#region Action
JobDetail(id: string) {
this.DisableForm();
this.isOnEdit = false;
this.isOpenModal = true;
this.isOnAdd = false;
this.jobService.getJob(id).pipe(
tap((res) => {
this.selectedJob = res.responseData;
}),
catchError((err) => {
console.log(err);
return of(null);
}),
finalize(() => this.loading = false)
).subscribe();
}
onDelete(id: string) {
this.jobService.deleteJob(id).pipe(
tap((res) => {
this.loading = true;
this.message = res.message;
}),
catchError((err) => {
console.log(err);
return of(null);
}),
finalize(() => (
this.loadDataFromServer(),
this.loading = false))
).subscribe();
}
onEdit(data: JobModel) {
this.EnableForm();
this.isOpenModal = true;
this.isOnEdit = true;
this.isOnAdd = false;
this.selectedJob = data;
}
addJob(){
this.EnableForm();
this.resetForm();
this.isOpenModal = true;
this.isOnEdit = false;
this.isOnAdd = true;
}
showDeleteConfirm(id : string): void {
this.modal.confirm({
nzTitle: 'Bạn có chắc rằng xóa công việc này không?',
nzOkText: 'Xóa',
nzOkType: 'primary',
nzOkDanger: true,
nzOnOk: () => this.onDelete(id),
nzCancelText: 'trở về',
});
}
//#endregion
}
...@@ -3,17 +3,10 @@ import { Route } from '@angular/router'; ...@@ -3,17 +3,10 @@ import { Route } from '@angular/router';
// file: job.routes.ts // file: job.routes.ts
const JOB_ROUTES: Route[] = [ const JOB_ROUTES: Route[] = [
{ {
path: 'jobLayout', path: 'Job',
loadComponent: () => loadComponent: () =>
import('./feature/jobLayout/jobLayout.component').then( import('../Job/Job.component').then(
(m) => m.JobLayoutComponent (m) => m.JobComponent
),
},
{
path: 'jobForm',
loadComponent: () =>
import('./feature/jobForm/jobForm.component').then(
(m) => m.JobFormComponent
), ),
}, },
]; ];
......
<nz-card nzTitle="Thêm công việc mới" [nzBordered]="true" style="width: 600px; margin: 0 auto; margin-top: 50px;">
<form nz-form (ngSubmit)="onSubmit()" #jobForm="ngForm">
<!-- Tiêu đề công việc -->
<nz-form-item>
<nz-form-label nzFor="title" [nzSpan]="6">Tiêu đề</nz-form-label>
<nz-form-control [nzSpan]="18">
<input
nz-input
type="text"
id="title"
name="title"
[(ngModel)]="job.title"
required
placeholder="Nhập tiêu đề công việc"
/>
</nz-form-control>
</nz-form-item>
<!-- Loại công việc -->
<nz-form-item>
<nz-form-label nzFor="type" [nzSpan]="6">Loại</nz-form-label>
<nz-form-control [nzSpan]="18">
<nz-select
id="type"
name="type"
[(ngModel)]="job.type"
required
placeholder="Chọn loại công việc"
>
<nz-option nzValue="Full-time" nzLabel="Full-time"></nz-option>
<nz-option nzValue="Part-time" nzLabel="Part-time"></nz-option>
<nz-option nzValue="Freelance" nzLabel="Freelance"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
<!-- Công ty -->
<nz-form-item>
<nz-form-label nzFor="company" [nzSpan]="6">Công ty</nz-form-label>
<nz-form-control [nzSpan]="18">
<input
nz-input
type="text"
id="company"
name="company"
[(ngModel)]="job.company"
required
placeholder="Nhập tên công ty"
/>
</nz-form-control>
</nz-form-item>
<!-- URL công ty -->
<nz-form-item>
<nz-form-label nzFor="company_url" [nzSpan]="6">Website</nz-form-label>
<nz-form-control [nzSpan]="18">
<input
nz-input
type="url"
id="company_url"
name="company_url"
[(ngModel)]="job.company_url"
placeholder="Nhập URL của công ty"
/>
</nz-form-control>
</nz-form-item>
<!-- Địa điểm -->
<nz-form-item>
<nz-form-label nzFor="location" [nzSpan]="6">Địa điểm</nz-form-label>
<nz-form-control [nzSpan]="18">
<input
nz-input
type="text"
id="location"
name="location"
[(ngModel)]="job.location"
required
placeholder="Nhập địa điểm công việc"
/>
</nz-form-control>
</nz-form-item>
<!-- Mô tả -->
<nz-form-item>
<nz-form-label nzFor="description" [nzSpan]="6">Mô tả</nz-form-label>
<nz-form-control [nzSpan]="18">
<textarea
nz-input
id="description"
name="description"
[(ngModel)]="job.description"
rows="4"
placeholder="Nhập mô tả công việc"
required
></textarea>
</nz-form-control>
</nz-form-item>
<!-- Nút Gửi -->
<nz-form-item>
<nz-form-control [nzSpan]="18" [nzOffset]="6">
<button nz-button nzType="primary" [disabled]="!jobForm.valid">
Thêm Công Việc
</button>
</nz-form-control>
</nz-form-item>
</form>
</nz-card>
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { NzBreadCrumbModule } from "ng-zorro-antd/breadcrumb";
import { NzButtonModule } from "ng-zorro-antd/button";
import { NzCardModule } from "ng-zorro-antd/card";
import { NzFormModule } from "ng-zorro-antd/form";
import { NzIconModule } from "ng-zorro-antd/icon";
import { NzLayoutModule } from "ng-zorro-antd/layout";
import { NzMenuModule } from "ng-zorro-antd/menu";
import { NzToolTipModule } from "ng-zorro-antd/tooltip";
import { NzSelectModule } from 'ng-zorro-antd/select';
import { FormsModule } from "@angular/forms";
import { JobModel } from "../../data-access/model/jobModel.model";
import { JobService } from "../../data-access/services/JobService.service";
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { finalize } from "rxjs";
@Component({
selector: "app-job-form",
templateUrl: "./jobForm.component.html",
standalone: true,
imports: [
CommonModule,
NzButtonModule,
NzBreadCrumbModule,
NzIconModule,
NzLayoutModule,
NzMenuModule,
NzToolTipModule,
NzCardModule,
NzFormModule,
NzSelectModule,
FormsModule
],
})
export class JobFormComponent {
job: JobModel = {
type: '',
created_at: new Date().toISOString(),
company: '',
company_url: '',
location: '',
title: '',
description: ''
};
constructor(private jobService: JobService, private notification: NzNotificationService) {}
onSubmit() {
console.log('Job Data:', this.job);
this.jobService.addingNewJob(this.job).pipe(
finalize(() => this.notification.success('Success', 'Thêm thành công'))
).subscribe();
}
}
<span class="tw-text-3xl tw-font-bold">Danh sách công việc</span>
<app-job-list></app-job-list>
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { RouterModule, RouterOutlet } from "@angular/router";
import { JobListComponent } from "../jobList/jobList.component";
import { JobFormComponent } from "../jobForm/jobForm.component";
@Component({
selector: "app-jobLayout",
templateUrl: "./jobLayout.component.html",
standalone: true,
imports: [
CommonModule,
RouterModule,
JobListComponent,
],
})
export class JobLayoutComponent {
constructor() {}
}
<nz-table #JobTable [nzData]="listOfJob"
[nzLoading]="loading"
[nzFrontPagination]="true"
nzShowSizeChanger
[nzPageSize]="5"
>
<thead>
<tr>
<th>STT</th>
<th>Loại công việc</th>
<th>Tên công việc</th>
<th>Công ty</th>
<th>Địa chỉ</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody>
@for (data of JobTable.data; track data) {
<tr>
<td>{{ listOfJob.indexOf(data) + 1 }}</td>
<td>{{ data.type }}</td>
<td>{{ data.title }}</td>
<td>{{ data.company }}</td>
<td>{{ data.location }}</td>
<td>
<nz-space nzSize="small">
<button nz-button class="tw-mr-2" nzType="default" nzSize="small" >Xem chi tiết</button>
<button class="tw-mr-2" nz-button nzType="primary" nzSize="small" (click)="onEdit(data)">Sửa</button>
<button nz-button nzType="primary" nzSize="small" nzDanger (click)="onDelete(data)">Xóa</button>
</nz-space>
</td>
</tr>
}
</tbody>
</nz-table>
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { JobService } from "../../data-access/services/JobService.service";
import { NzTableModule, NzTableQueryParams } from 'ng-zorro-antd/table';
import { catchError, finalize, of, tap } from "rxjs";
import { JobModel } from "../../data-access/model/jobModel.model";
import { NzDividerModule } from 'ng-zorro-antd/divider';
import { NzButtonModule } from "ng-zorro-antd/button";
import { NzSpaceModule } from 'ng-zorro-antd/space';
import { NzNotificationService } from 'ng-zorro-antd/notification';
@Component({
selector: "app-job-list",
templateUrl: "./jobList.component.html",
standalone: true,
imports: [CommonModule,
NzTableModule,
NzDividerModule,
NzButtonModule,
NzSpaceModule
],
})
export class JobListComponent implements OnInit {
total = 1;
listOfJob: JobModel[] = [];
loading = true;
pageSize = 15;
pageIndex = 1;
loadDataFromServer(
pageIndex: number,
pageSize: number
): void {
this.loading = true;
this.jobService.jobsGet(pageIndex, pageSize).pipe(
tap((res) => {
this.loading = false;
this.total = res.total;
this.listOfJob = res.responseData.items;
console.log(this.listOfJob);
}),
catchError((err) => {
console.log(err);
return of(null);
})
).subscribe();
}
constructor(private jobService: JobService,private notification: NzNotificationService) {}
ngOnInit(): void {
this.loadDataFromServer(this.pageIndex, this.pageSize);
}
onEdit(data: any) {
console.log(data);
}
onDelete(data: any) {
this.jobService.deleteJob(data.id).pipe(
finalize(() => {
this.notification.success('Success', 'Xóa thành công');
this.listOfJob = this.listOfJob.filter(job => job.id !== data.id);
})
).subscribe();
}
}
...@@ -11,7 +11,7 @@ const ADMIN_ROUTES: Route[] = [ ...@@ -11,7 +11,7 @@ const ADMIN_ROUTES: Route[] = [
children: [ children: [
{ {
path: '', path: '',
loadChildren: () => import('./Management/job/job.routes'), loadChildren: () => import('./Management/Job/Management.routes'),
}, },
] ]
} }
......
export interface JobModel { export interface JobModel {
id?: string; id: string;
type?: string; type?: string;
created_at: string; created_at: string;
company?: string; company?: string;
......
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpRequest } from '@angular/common/http'; import { HttpClient, HttpHeaders, HttpParams, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { environment } from '../../../../../../environments/environment.development'; import { environment } from '../../../../environments/environment.development';
import { JobModel } from '../model/jobModel.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class JobService { export class JobService {
apiUrl = environment.API_DOMAIN + '/jobs'; apiUrl = environment.API_DOMAIN + '/jobs';
apiUrlById = environment.API_DOMAIN + '/jobById';
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
jobsGet(pageNumber: number, pageSize: number): Observable<any> { jobsGet(): Observable<any> {
const params = new HttpParams() const params = new HttpParams()
.set('pageNumber', pageNumber.toString())
.set('pageSize', pageSize.toString());
return this.http.get<any>(this.apiUrl, { params }); return this.http.get<any>(this.apiUrl, { params });
} }
addingNewJob(data: any): Observable<any> { addingNewJob(data: JobModel): Observable<any> {
const headers = new HttpHeaders() const headers = new HttpHeaders()
.set('Content-Type', 'application/json') .set('Content-Type', 'application/json')
.set('authorization', `Bearer ${localStorage.getItem('token')}`); .set('authorization', `Bearer ${localStorage.getItem('token')}`);
...@@ -29,5 +30,20 @@ export class JobService { ...@@ -29,5 +30,20 @@ export class JobService {
const url = `${this.apiUrl}/${id}`; const url = `${this.apiUrl}/${id}`;
return this.http.delete<any>(url, { headers }); return this.http.delete<any>(url, { headers });
} }
editJob(data: JobModel): Observable<any> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
const url = `${this.apiUrl}/${data.id}`;
return this.http.put<any>(url, data, { headers });
}
getJob(id: string): Observable<any> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
const url = `${this.apiUrlById}/${id}`;
console.log(id, url);
return this.http.get<any>(url, { headers });
}
} }
<header class="tw-flex tw-justify-between tw-items-center tw-p-4 tw-bg-gray-900">
<h3 class="tw-text-2xl">Role: {{role}}</h3>
</header>
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { NzLayoutModule } from 'ng-zorro-antd/layout'; import { NzLayoutModule } from 'ng-zorro-antd/layout';
@Component({ @Component({
selector: 'meu-header', selector: 'meu-admin-header',
standalone: true, standalone: true,
imports: [CommonModule,NzLayoutModule], imports: [CommonModule,NzLayoutModule],
templateUrl: './header.component.html', templateUrl: './header.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class HeaderComponent implements OnInit { export class HeaderComponent implements OnInit {
@Input() role = '';
ngOnInit(): void {} ngOnInit(): void {}
} }
<nz-layout> <nz-layout>
<nz-sider nzCollapsible [(nzCollapsed)]="isCollapsed" [nzTrigger]="null"> <nz-sider nzCollapsible [nzTrigger]="null">
<div class="logo"></div> <div class="logo"></div>
<ul nz-menu nzTheme="dark" nzMode="inline"> <ul nz-menu nzTheme="dark" nzMode="inline">
<li nz-submenu nzTitle="Quản lí công việc" nzIcon="user"> <li nz-menu-item routerLink="/admin/job-management/Job">
<ul> Danh sách công việc
<li </li>
nz-menu-item <li nz-menu-item (click)="logout()">
routerLink="/admin/job-management/jobLayout" <span nz-icon nzType="logout" nzTheme="outline"></span>
> Đăng xuất
Danh sách công việc
</li>
<li
nz-menu-item
routerLink="/admin/job-management/jobForm"
>
Thêm công việc
</li>
</ul>
</li> </li>
<li nz-menu-item nzIcon="close" (click)="logout()">
Đăng xuất
</li>
</ul> </ul>
</nz-sider> </nz-sider>
<nz-layout> <nz-layout>
<meu-header></meu-header> <meu-admin-header role={{role}}></meu-admin-header>
<nz-header>
<span
class="trigger"
nz-icon
[nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'"
(click)="isCollapsed = !isCollapsed"
></span>
</nz-header>
<nz-content> <nz-content>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</nz-content> </nz-content>
......
import { RouterLink, RouterModule, RouterOutlet } from '@angular/router'; import { defaultUrlMatcher, RouterLink, RouterModule, RouterOutlet } from '@angular/router';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { NzLayoutModule } from 'ng-zorro-antd/layout'; import { NzLayoutModule } from 'ng-zorro-antd/layout';
...@@ -33,9 +33,10 @@ import { NzToolTipModule } from 'ng-zorro-antd/tooltip'; ...@@ -33,9 +33,10 @@ import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AdminLayoutComponent implements OnInit { export class AdminLayoutComponent implements OnInit {
role = localStorage.getItem('role');
constructor() {} constructor() {}
isCollapsed = false; ngOnInit(): void {
ngOnInit(): void {} }
logout() { logout() {
localStorage.clear(); localStorage.clear();
window.location.href = '/login'; window.location.href = '/login';
......
<nz-card nzHoverable style="width:240px" [nzCover]="coverTemplate">
<nz-card-meta nzTitle="Europe Street beat" nzDescription="www.instagram.com"></nz-card-meta>
</nz-card>
<ng-template #coverTemplate>
<img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />
</ng-template>
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { NzCardModule } from "ng-zorro-antd/card";
@Component({
selector: 'job-card',
templateUrl: './JobCard.component.html',
imports: [NzCardModule],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobCardComponent {
}
<nz-divider nzOrientation="left" nzText="Vertical"></nz-divider>
<div nz-row [nzGutter]="[16, 24]" ngFor="let index of listOfJob">
<job-card></job-card>
</div>
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { JobCardComponent } from "../JobCard/JobCard.component";
import { NzDividerModule } from "ng-zorro-antd/divider";
import { NzGridModule } from "ng-zorro-antd/grid";
import { JobModel } from "../../../+admin/data-access/model/jobModel.model";
@Component({
selector: 'app-job-card',
templateUrl: './JobListCard.component.html',
imports: [
NzDividerModule,
NzGridModule,
JobCardComponent,
],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobListCardComponent {
listOfJob: JobModel[] = [];
}
<div>home</div> <meu-home-header></meu-home-header>
<meu-home-footer></meu-home-footer>
...@@ -7,6 +7,8 @@ import { NzMenuModule } from 'ng-zorro-antd/menu'; ...@@ -7,6 +7,8 @@ import { NzMenuModule } from 'ng-zorro-antd/menu';
import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzIconModule } from 'ng-zorro-antd/icon';
import { HeaderComponent } from '../../+shell/ui/components/header/feature/header.component'; import { HeaderComponent } from '../../+shell/ui/components/header/feature/header.component';
import { FooterComponent } from "../../+shell/ui/components/footer/feature/footer.component"; import { FooterComponent } from "../../+shell/ui/components/footer/feature/footer.component";
import { HeaderHomeComponent } from '../layout/feature/Components/header/header.component';
import { FooterHomeComponent } from '../layout/feature/Components/footer/footer.component';
@Component({ @Component({
...@@ -19,8 +21,8 @@ import { FooterComponent } from "../../+shell/ui/components/footer/feature/foote ...@@ -19,8 +21,8 @@ import { FooterComponent } from "../../+shell/ui/components/footer/feature/foote
NzBreadCrumbModule, NzBreadCrumbModule,
NzMenuModule, NzMenuModule,
NzIconModule, NzIconModule,
HeaderComponent, HeaderHomeComponent,
FooterComponent FooterHomeComponent
], ],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'], styleUrls: ['./home.component.scss'],
......
<nz-footer>
<footer class="tw-bg-gray-800 tw-py-8">
<div class="tw-container tw-mx-auto">
<div class="tw-flex tw-justify-between tw-items-center tw-px-4">
<!-- Tên công ty -->
<div class="tw-text-2xl tw-font-bold">MeU Solution</div>
<!-- Liên kết menu -->
<div class="tw-hidden tw-lg:flex">
<ul class="tw-flex tw-space-x-6">
<li><a href="/about" class="tw-text-gray-400 hover:tw-text-white">Giới thiệu</a></li>
<li><a href="/services" class="tw-text-gray-400 hover:tw-text-white">Dịch vụ</a></li>
<li><a href="/contact" class="tw-text-gray-400 hover:tw-text-white">Liên hệ</a></li>
<li><a href="/blog" class="tw-text-gray-400 hover:tw-text-white">Blog</a></li>
</ul>
</div>
<!-- Mạng xã hội -->
<div class="tw-flex tw-space-x-4">
<a href="https://facebook.com" target="_blank" class="tw-text-gray-400 hover:tw-text-white"><i class="fa fa-facebook"></i></a>
<a href="https://twitter.com" target="_blank" class="tw-text-gray-400 hover:tw-text-white"><i class="fa fa-twitter"></i></a>
<a href="https://instagram.com" target="_blank" class="tw-text-gray-400 hover:tw-text-white"><i class="fa fa-instagram"></i></a>
</div>
</div>
<!-- Bản quyền và địa chỉ -->
<div class="tw-border-t tw-border-gray-700 tw-mt-6 tw-pt-6 tw-text-center">
<p class="tw-text-sm">&copy; 2024 Công ty ABC. Tất cả quyền lợi được bảo lưu.</p>
<p class="tw-text-sm">Địa chỉ: 123 Đường ABC, Quận 1, Thành phố Hồ Chí Minh, Việt Nam</p>
</div>
</div>
</footer>
</nz-footer>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
@Component({
selector: 'meu-home-footer',
standalone: true,
imports: [CommonModule,
NzLayoutModule,
NzIconModule
],
templateUrl: './footer.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterHomeComponent implements OnInit {
ngOnInit(): void {}
}
<header class="tw-bg-gray-900 tw-py-4">
<div class="tw-container tw-mx-auto tw-flex tw-justify-between tw-items-center tw-px-4">
<div class="tw-text-3xl tw-font-bold">
<a href="/" class="hover:tw-text-gray-400">MeU Solution</a>
</div>
</div>
<nav class="tw-bg-gray-900 tw-py-2">
<div class="tw-container tw-mx-auto">
<ul class="tw-flex tw-space-x-8 tw-justify-center">
<li><a href="/#" class="tw-text-gray-300 hover:tw-text-white">Giới thiệu</a></li>
<li><a href="/#" class="tw-text-gray-300 hover:tw-text-white">Dịch vụ</a></li>
<li><a href="/#" class="tw-text-gray-300 hover:tw-text-white">Liên hệ</a></li>
<li><a href="/#" class="tw-text-gray-300 hover:tw-text-white">Blog</a></li>
</ul>
</div>
</nav>
</header>
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
@Component({
selector: 'meu-home-header',
standalone: true,
imports: [CommonModule,NzLayoutModule],
templateUrl: './header.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HeaderHomeComponent implements OnInit {
ngOnInit(): void {}
}
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable, tap } from 'rxjs';
import { environment } from '../../../../environments/environment.development'; import { environment } from '../../../../environments/environment.development';
import { Router } from '@angular/router';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
private apiUrl = environment.API_DOMAIN + '/login'; private apiUrl = environment.API_DOMAIN + '/login';
constructor(private http: HttpClient) {} constructor(private http: HttpClient, private router: Router) {}
login(username: string, password: string): Observable<any> { login(username: string, password: string): Observable<any> {
const headers = new HttpHeaders({ 'Content-Type': 'application/json' }); const headers = new HttpHeaders({ 'Content-Type': 'application/json' });
const body = { username, password }; const body = { username, password };
return this.http.post<any>(this.apiUrl, body, { headers }).pipe(
tap((response) => {
localStorage.setItem('token', response.responseData.token);
localStorage.setItem('expirationTime', response.responseData.expirationTime);
localStorage.setItem('role', response.responseData.role);
})
);
}
logout(): void {
localStorage.removeItem('token');
localStorage.removeItem('expirationTime');
localStorage.removeItem('role');
this.router.navigate(['/login']);
}
isLoggedIn(): boolean {
const token = localStorage.getItem('token');
if (token) {
return true;
}
return false;
}
loginSussces(): void {
this.getUserRole() === "admin" ? this.router.navigate(['/admin']) : this.router.navigate(['/home']);
}
return this.http.post<any>(this.apiUrl, body, { headers }); getUserRole(): string | null {
return localStorage.getItem('role');
} }
} }
...@@ -11,7 +11,7 @@ export class AdminGuard implements CanActivate { ...@@ -11,7 +11,7 @@ export class AdminGuard implements CanActivate {
if (role === 'admin') { if (role === 'admin') {
return true; return true;
} else { } else {
this.router.navigate(['/login']); // chuyển đến trang lỗi không có quyền. cho bắt đăng nhập với quyên admin this.router.navigate(['/home']);
return false; return false;
} }
} }
......
...@@ -9,12 +9,16 @@ export class AuthGuard implements CanActivate { ...@@ -9,12 +9,16 @@ export class AuthGuard implements CanActivate {
canActivate(): boolean { canActivate(): boolean {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (token) { if (!token) {
this.router.navigate(['/admin']); this.router.navigate(['/login']);
return false; return false;
} else { } else {
const currentRoute = this.router.url;
if (currentRoute === '/login') {
this.router.navigate(['/home']);
return false;
}
return true; return true;
} }
} }
} }
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../Services/Auth.Service';
@Injectable({
providedIn: 'root',
})
export class LoginGuard implements CanActivate {
constructor(private _service: AuthService, private _router: Router) {}
canActivate(): boolean {
if (this._service.isLoggedIn()) {
this._router.navigate(['/home']);
return false;
}
return true;
}
}
...@@ -15,19 +15,7 @@ ...@@ -15,19 +15,7 @@
</nz-input-group> </nz-input-group>
</nz-form-control> </nz-form-control>
</nz-form-item> </nz-form-item>
<div nz-row class="login-form-margin"> <p *ngIf="MgsError" class="error-message tw-text-center">{{MgsError}}</p>
<div nz-col [nzSpan]="12">
<label nz-checkbox formControlName="remember">
<span>Remember me</span>
</label>
</div>
<div nz-col [nzSpan]="12">
<a class="login-form-forgot">Forgot password</a>
</div>
</div>
<button nz-button class="login-form-button login-form-margin" [nzType]="'primary'">Log in</button> <button nz-button class="login-form-button login-form-margin" [nzType]="'primary'">Log in</button>
<div class="text-center mt-4">
Or <a>register now!</a>
</div>
</form> </form>
</div> </div>
...@@ -11,8 +11,8 @@ import { NzAlertModule } from 'ng-zorro-antd/alert'; ...@@ -11,8 +11,8 @@ import { NzAlertModule } from 'ng-zorro-antd/alert';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { catchError, finalize, Observable, of, tap } from 'rxjs'; import { catchError, finalize, Observable, of, tap } from 'rxjs';
import { AuthService } from '../data-access/Services/ApiService.service';
import { response } from 'express'; import { response } from 'express';
import { AuthService } from '../data-access/Services/Auth.Service';
@Component({ @Component({
selector: 'meu-login', selector: 'meu-login',
standalone: true, standalone: true,
...@@ -44,30 +44,22 @@ export class LoginComponent implements OnInit { ...@@ -44,30 +44,22 @@ export class LoginComponent implements OnInit {
}); });
} }
ngOnInit() {} ngOnInit() {}
saveInfo(res: any) {
localStorage.setItem('token', res.responseData.token);
localStorage.setItem('expirationTime', res.responseData.expirationTime);
localStorage.setItem('role', res.responseData.role);
}
onSubmit() { onSubmit() {
if (!this.loginForm.valid) { if (!this.loginForm.valid) {
this.MgsError = "Vui lòng nhập thng tin đăng nhập"; this.MgsError = "Vui lòng nhập thông tin đăng nhập";
return; return;
} }
console.log(this.loginForm.value.userName, this.loginForm.value.password);
this.authService.login(this.loginForm.value.userName, this.loginForm.value.password).pipe( this.authService.login(this.loginForm.value.userName, this.loginForm.value.password).pipe(
tap((res) => { // không ảnh hưởng đến data trên observable
this.saveInfo(res);
window.location.href = '/admin';
}),
catchError((err) => { catchError((err) => {
this.MgsError = err.message; this.MgsError = err.message;
console.log(this.MgsError); console.log(this.MgsError);
console.log(err); console.log(err);
return of(null); return of(null);
}), }),
finalize(() => { // gọi ra khi observable hoàn tất finalize(() => {
if(this.authService.isLoggedIn())
this.authService.loginSussces();
console.log('Login request completed'); console.log('Login request completed');
}) })
).subscribe(); ).subscribe();
......
...@@ -3,6 +3,7 @@ import { LayoutComponent } from '../ui/layout.component'; ...@@ -3,6 +3,7 @@ import { LayoutComponent } from '../ui/layout.component';
import { LoginComponent } from '../../+login/feature/login.component'; import { LoginComponent } from '../../+login/feature/login.component';
import { AuthGuard } from '../../+login/data-access/guard/auth.guard'; import { AuthGuard } from '../../+login/data-access/guard/auth.guard';
import { AdminGuard } from '../../+login/data-access/guard/admin.guard'; import { AdminGuard } from '../../+login/data-access/guard/admin.guard';
import { LoginGuard } from '../../+login/data-access/guard/login.guard';
export const shellRoutes: Routes = [ export const shellRoutes: Routes = [
{ {
...@@ -16,7 +17,7 @@ export const shellRoutes: Routes = [ ...@@ -16,7 +17,7 @@ export const shellRoutes: Routes = [
}, },
{ {
path: 'home', path: 'home',
canActivate: [], //not have guard yet, set up later canActivate: [],
loadChildren: () => import('../../+home/home.routes'), loadChildren: () => import('../../+home/home.routes'),
}, },
], ],
...@@ -34,6 +35,13 @@ export const shellRoutes: Routes = [ ...@@ -34,6 +35,13 @@ export const shellRoutes: Routes = [
{ {
path: 'login', path: 'login',
component: LoginComponent, component: LoginComponent,
canActivate: [AuthGuard], children: [
{
path: '',
canActivate: [LoginGuard],
loadChildren: () => import('../../+login/login.routes'),
}
]
}, },
]; ];
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment