Commit 5465e871 authored by tinhbe's avatar tinhbe

update

parent d3f8c729
<nz-modal
[(nzVisible)]="showViewModalComponent"
nzTitle="Chi tiết công việc"
(nzOnCancel)="handleCancel()"
(nzOnOk)="handleOk()"
>
<ng-content *nzModalContent>
<div *ngIf="jobData">
<div class="tw-flex tw-flex-col tw-space-y-4">
<div *ngIf="jobData.company_logo">
<img
[src]="jobData.company_logo"
alt="{{ jobData.company }}"
class="tw-w-32 tw-h-32 tw-object-contain"
/>
</div>
<div><strong>Loại công việc:</strong> {{ jobData.type || "N/A" }}</div>
<div><strong>Tiêu đề:</strong> {{ jobData.title || "N/A" }}</div>
<div>
<strong>Công ty:</strong>
<a
*ngIf="jobData.company_url"
[href]="jobData.company_url"
target="_blank"
rel="noopener noreferrer"
>
{{ jobData.company || "N/A" }}
</a>
<span *ngIf="!jobData.company_url">
{{ jobData.company || "N/A" }}
</span>
</div>
<div><strong>Địa chỉ:</strong> {{ jobData.location || "N/A" }}</div>
<div>
<strong>Mô tả:</strong>
<div
[innerHTML]="getSanitizedDescription(jobData.description || 'N/A')"
></div>
</div>
<div>
<strong>Cách thức ứng tuyển:</strong>
<div
[innerHTML]="getSanitizedHowToApply(jobData.how_to_apply || 'N/A')"
></div>
</div>
<div>
<strong>Ngày tạo:</strong> {{ jobData.created_at | date : "short" }}
</div>
</div>
</div>
</ng-content>
</nz-modal>
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { CommonModule, DatePipe } from '@angular/common';
import { JobModel } from '../../../../data-access/model/jobModel.model';
@Component({
selector: 'app-view-job',
templateUrl: './JobDetail.component.html',
standalone: true,
imports: [
NzModalModule,
NzButtonModule,
DatePipe,
CommonModule
]
})
export class ViewJobComponent {
@Input() showViewModalComponent: boolean = false;
@Output() close = new EventEmitter<void>();
@Input() jobData: JobModel.Job | null = null;
constructor(private sanitizer: DomSanitizer) {}
handleOk(): void {
this.close.emit();
}
handleCancel(): void {
this.close.emit();
}
getSanitizedDescription(description: string | null | undefined): SafeHtml {
return description ? this.sanitizer.bypassSecurityTrustHtml(description) : 'N/A';
}
getSanitizedHowToApply(how_to_apply: string | null | undefined): SafeHtml {
return how_to_apply ? this.sanitizer.bypassSecurityTrustHtml(how_to_apply) : 'N/A';
}
}
<nz-modal
[(nzVisible)]="showModal"
[nzTitle]="(isEdit)? 'Chi tiết công vic': 'Thêm mi công vic'"
(nzOnCancel)="onCancel()"
(nzOnOk)="onSubmit()"
[nzMaskClosable]="false"
>
<ng-content *nzModalContent>
<form
nz-form
[formGroup]="JobForm"
class="tw-p-6 tw-bg-white tw-rounded-lg tw-shadow-md tw-space-y-6"
>
<nz-form-item>
<nz-form-label [nzSpan]="7">
Loại công việc
</nz-form-label>
<nz-form-control
[nzSpan]="17"
[nzErrorTip]="getErrorTip('type')"
>
<nz-select formControlName="type" placeholder="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>
<nz-form-item>
<nz-form-label [nzSpan]="7">
Tiêu đề
</nz-form-label>
<nz-form-control
[nzSpan]="17"
[nzErrorTip]="getErrorTip('title')"
>
<input
nz-input
formControlName="title"
placeholder="Tiêu đề"
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-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">
Công ty
</nz-form-label>
<nz-form-control
[nzSpan]="17"
[nzErrorTip]="getErrorTip('company')"
>
<input
nz-input
formControlName="company"
placeholder="Công ty"
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-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">Địa chỉ</nz-form-label>
<nz-form-control
[nzSpan]="17"
[nzErrorTip]="getErrorTip('location')"
>
<input
nz-input
formControlName="location"
placeholder="Địa chỉ"
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-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7">
Mô tả
</nz-form-label>
<nz-form-control
[nzSpan]="17"
[nzErrorTip]="getErrorTip('description')"
>
<textarea
nz-input
formControlName="description"
placeholder="Mô tả công việc"
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"
></textarea>
</nz-form-control>
</nz-form-item>
</form>
</ng-content>
</nz-modal>
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges
} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NzModalModule } from 'ng-zorro-antd/modal';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { ReactiveFormsModule } from '@angular/forms';
import { NzMessageService } from 'ng-zorro-antd/message';
import { CommonModule } from '@angular/common';
import { JobModel } from '../../../../data-access/model/jobModel.model';
import { JobService } from '../../../../data-access/services/JobService.service';
import { catchError, of, tap } from 'rxjs';
@Component({
selector: 'app-modal-job',
templateUrl: `Modal.component.html`,
standalone: true,
imports: [
NzModalModule,
NzFormModule,
NzInputModule,
NzSelectModule,
NzButtonModule,
ReactiveFormsModule,
CommonModule
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalJobComponent implements OnChanges {
@Input() isEdit: boolean = false;
@Input() showModal: boolean = false;
@Input() jobData: JobModel.Job | null = null;
@Output() submit = new EventEmitter<void>();
@Output() close = new EventEmitter<void>();
JobForm: FormGroup;
isSubmitting: boolean = false;
constructor(
private fb: FormBuilder,
private jobService: JobService,
private message: NzMessageService
) {
this.JobForm = this.fb.group({
type: [null, [Validators.required]],
title: [null, [Validators.required]],
company: [null, [Validators.required]],
location: [null],
description: [null, [Validators.required]],
how_to_apply: [null],
company_logo: [null],
});
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['jobData'] && this.isEdit && this.jobData) {
this.JobForm.patchValue({
type: this.jobData.type,
title: this.jobData.title,
company: this.jobData.company,
location: this.jobData.location,
description: this.jobData.description,
how_to_apply: this.jobData.how_to_apply,
company_logo: this.jobData.company_logo,
});
}
if (changes['showModal'] && !this.showModal) {
this.JobForm.reset();
}
}
getErrorTip(controlName: string): string {
const control = this.JobForm.get(controlName);
return control?.errors ? 'vui lòng nhập trường ' + controlName : '';
}
onSubmit(): void {
this.isEdit ? this.onSubmitEditJob() : this.onSubmitAddJob();
}
onSubmitEditJob(): void {
if (this.JobForm.valid && this.jobData) {
this.isSubmitting = true;
const jobToUpdate: JobModel.Job = {
...this.jobData,
...this.JobForm.value,
};
this.jobService.editJob(jobToUpdate).pipe(
tap((res) => {
this.isSubmitting = false;
this.message.success(res.message || 'Cập nhật công việc thành công!', {
nzDuration: 2500,
});
this.submit.emit();
this.onCancel();
}),
catchError((err) => {
this.isSubmitting = false;
this.message.error(
err.message || 'Đã xảy ra lỗi khi cập nhật công việc.',
{ nzDuration: 2500 }
);
return of(null);
})
)
.subscribe();
}
}
onSubmitAddJob(): void {
if (this.JobForm.invalid) {
Object.values(this.JobForm.controls).forEach(control => {
if (control.invalid) {
control.markAsDirty();
control.updateValueAndValidity();
}
});
return;
}
const newJob: JobModel.JobRequest = this.JobForm.value;
console.log(newJob);
this.jobService
.addingNewJob(newJob)
.pipe(
tap((res) => {
this.message.success(res.message, { nzDuration: 2500 });
this.submit.emit();
this.JobForm.reset();
this.showModal = false;
}),
catchError((err) => {
this.message.error(err.message, { nzDuration: 2500 });
return of(null);
})
)
.subscribe();
}
onCancel(): void {
this.close.emit();
this.JobForm.reset();
}
}
<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 class="tw-container tw-content-center">
<div class="tw-flex tw-justify-end tw-pb-5 tw-pt-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"
[nzScroll]="{ x: '300px' }"
>
<thead>
<tr>
......@@ -21,16 +23,21 @@
</tr>
</thead>
<tbody>
@for (data of JobTable.data; track data) {
<tr>
<td>{{ listOfJob.indexOf(data) + 1 }}</td>
<tr *ngFor="let data of JobTable.data; let i = index">
<td>{{ i + 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)">
<button
class="tw-mr-2"
nz-button
nzType="primary"
nzSize="small"
(click)="JobDetail(data.id)"
>
Xem chi tiết
</button>
<button
......@@ -42,61 +49,31 @@
>
Sửa
</button>
<button nz-button nzType="dashed" nzSize="small" nzDanger (click)="showDeleteConfirm(data.id)">
<button
nz-button
nzType="dashed"
nzSize="small"
nzDanger
(click)="showDeleteConfirm(data.id)"
>
Xóa
</button>
</nz-space>
</td>
</tr>
}
</tbody>
</nz-table>
<app-modal-job
[showModal]="showModal"
[jobData]="currentEditJob"
[isEdit]="isEdit"
(close)="onCloseModal()"
(submit)="onSubmit()"
></app-modal-job>
<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>
<app-view-job
[showViewModalComponent]="showViewModalComponent"
[jobData]="currentViewJob"
(close)="onCloseViewModal()"
></app-view-job>
</div>
//#region imports
import { CommonModule } from "@angular/common";
import { Component, OnInit } from "@angular/core";
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, OnInit } from "@angular/core";
import { JobService } from "../../data-access/services/JobService.service";
import { NzTableModule, NzTableQueryParams } from 'ng-zorro-antd/table';
import { NzTableModule } 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 {FormsModule, ReactiveFormsModule } from "@angular/forms";
import { NzFormModule } from "ng-zorro-antd/form";
import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzAlertModule } from "ng-zorro-antd/alert";
import { NzNotificationService } from 'ng-zorro-antd/notification';
import { ViewJobComponent } from "./Action/JobDetail/JobDetail.component";
import { ModalJobComponent } from "./Action/ModalJob/Modal.component";
//#endregion
//#region Component
@Component({
selector: "meu-admin-job",
templateUrl: "./Job.component.html",
standalone: true,
imports: [CommonModule,
imports: [
CommonModule,
NzTableModule,
NzDividerModule,
NzButtonModule,
NzSpaceModule,
NzModalModule,
NzModalModule,
NzFormModule,
ReactiveFormsModule,
NzSelectModule
NzSelectModule,
FormsModule,
NzAlertModule,
ViewJobComponent,
ModalJobComponent
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
//#endregion
export class JobComponent implements OnInit {
isMobileView: boolean = false;
searchTerm: string = '';
//#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]),
});
selectedJob?: JobModel.Job;
listOfJob: JobModel.Job[] = [];
showModal: boolean = false;
isEdit : boolean = false;
showViewModalComponent: boolean = false;
// Công việc hiện tại để chỉnh sửa hoặc xem chi tiết
currentEditJob: JobModel.Job | null = null;
currentViewJob: JobModel.Job | null = null;
//#endregion
//#region LoadData
constructor(private fb: FormBuilder, private jobService: JobService, private modal: NzModalService) { }
constructor(
private _cdr: ChangeDetectorRef,
private jobService: JobService,
private modal: NzModalService,
private message: NzMessageService,
private notification: NzNotificationService
) { }
loadDataFromServer(): void {
console.log("hello");
this.loading = true;
this.jobService.jobsGet().pipe(
tap((res) => {
if (res.responseData)
if (res.responseData) {
this.listOfJob = res.responseData.rows;
}
this._cdr.detectChanges();
}),
catchError((err) => {
this.listOfJob = [];
this.notification.error('Lỗi', 'Không tải được dữ liệu công việc.');
return of(null);
}),
finalize(() => (this.loading = false))
})
).subscribe();
}
ngOnInit(): void {
this.loadDataFromServer();
this.checkScreenSize(); // Kiểm tra kích thước màn hình ban đầu
}
//#endregion
//#region Handler
resetForm() {
this.validateJobForm.reset();
this.selectedJob = {} as JobModel;
onCloseViewModal(): void {
this.showViewModalComponent = false;
}
EnableForm() {
this.validateJobForm.enable();
onSubmit(): void {
this.isEdit = false;
this.showModal = false;
this._cdr.detectChanges();
this.loadDataFromServer();
}
DisableForm() {
this.validateJobForm.disable();
onCloseModal(): void {
this.showModal = false;
}
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(
//#endregion
//#region Handlers
// Handler xem chi tiết công việc
handleViewJob(id: string): void {
this.jobService.getJobById(id)
.pipe(
tap((res) => {
this.message = res.message;
this.loadDataFromServer();
this.showViewModalComponent = true;
this.currentViewJob = res.responseData as JobModel.Job;
this._cdr.detectChanges();
}),
catchError((err) => {
console.error(err);
this.message.error(err.message || 'An error occurred while fetching job data.', { nzDuration: 2500 });
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(
)
.subscribe();
}
// Handler xóa công việc
onDelete(id: string): void {
this.jobService.deleteJob(id)
.pipe(
tap((res) => {
this.message = res.message;
this.message.success(res.message, { nzDuration: 2500 });
this._cdr.markForCheck();
this.loadDataFromServer();
}),
catchError((err) => {
console.error(err);
this.message.error(err.message, { nzDuration: 2500 });
return of(null);
})
).subscribe();
}
this.isOpenModal = false;
this.isOnEdit = false;
this.isOnAdd = false;
)
.subscribe();
}
onCancel(): void {
this.isOpenModal = false;
this.isOnEdit = false;
// Xử lý khi nhấn nút "Thêm công việc"
addJob(): void {
this.showModal = true;
}
//#endregion
//#region Action
JobDetail(id: string) {
this.DisableForm();
this.isOnEdit = false;
this.isOpenModal = true;
this.isOnAdd = false;
this.jobService.getJob(id).pipe(
tap((res) => {
console.log(res);
this.selectedJob = res.responseData as JobModel;
}),
catchError((err) => {
console.log(err);
return of(null);
}),
finalize(() => (
this.loadDataFromServer(), 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;
// Xử lý khi nhấn nút "Sửa"
onEdit(data: JobModel.Job): void {
this.isEdit = true;
this.currentEditJob = data;
this.showModal= true;
}
addJob() {
this.EnableForm();
this.resetForm();
this.isOpenModal = true;
this.isOnEdit = false;
this.isOnAdd = true;
// Xử lý khi nhấn nút "Xem chi tiết"
JobDetail(id: string): void {
this.handleViewJob(id);
}
// Hiển thị hộp thoại xác nhận xóa
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?',
......@@ -204,8 +160,25 @@ export class JobComponent implements OnInit {
nzOkType: 'primary',
nzOkDanger: true,
nzOnOk: () => this.onDelete(id),
nzCancelText: 'trở về',
nzCancelText: 'Trở về',
});
}
//#endregion
//#region HostListener
@HostListener('window:resize', ['$event'])
onResize(event: any) {
this.checkScreenSize();
}
checkScreenSize() {
this.isMobileView = window.innerWidth < 768;
this._cdr.detectChanges();
}
isMobile(): boolean {
return this.isMobileView;
}
//#endregion
}
export interface JobModel {
export namespace JobModel {
export interface Job {
id: string;
type?: string;
created_at: string;
......@@ -9,4 +10,14 @@ export interface JobModel {
description?: string;
how_to_apply?: string;
company_logo?: string;
}
export interface JobRequest{
type: string;
created_at: string;
company: string;
company_url: string;
location: string;
title: string;
description: string;
}
}
......@@ -14,37 +14,24 @@ export class JobService {
apiUrlById = environment.API_DOMAIN + '/jobById';
constructor(private http: HttpClient) {}
jobsGet(): Observable<ResponseResult<Rows<JobModel>>> {
return this.http.get<ResponseResult<Rows<JobModel>>>(this.apiUrl);
jobsGet(): Observable<ResponseResult<Rows<JobModel.Job>>> {
return this.http.get<ResponseResult<Rows<JobModel.Job>>>(this.apiUrl);
}
addingNewJob(data: JobModel): Observable<ResponseResult<JobModel>> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
return this.http.post<ResponseResult<JobModel>>(this.apiUrl, data, { headers });
addingNewJob(data: JobModel.JobRequest): Observable<ResponseResult<JobModel.Job>> {
return this.http.post<ResponseResult<JobModel.Job>>(this.apiUrl, data);
}
deleteJob(id: string): Observable<ResponseResult<JobModel>> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
deleteJob(id: string): Observable<ResponseResult<JobModel.Job>> {
const url = `${this.apiUrl}/${id}`;
return this.http.delete<ResponseResult<JobModel>>(url, { headers });
return this.http.delete<ResponseResult<JobModel.Job>>(url);
}
editJob(data: JobModel): Observable<ResponseResult<JobModel>> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
editJob(data: JobModel.Job): Observable<ResponseResult<JobModel.Job>> {
const url = `${this.apiUrl}/${data.id}`;
return this.http.put<ResponseResult<JobModel>>(url, data, { headers });
return this.http.put<ResponseResult<JobModel.Job>>(url, data);
}
getJob(id: string): Observable<ResponseResult<JobModel>> {
const headers = new HttpHeaders()
.set('Content-Type', 'application/json')
.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
getJobById(id: string): Observable<ResponseResult<JobModel.Job>> {
const url = `${this.apiUrlById}/${id}`;
console.log(id, url);
return this.http.get<ResponseResult<JobModel>>(url, { headers });
return this.http.get<ResponseResult<JobModel.Job>>(url);
}
}
......@@ -5,23 +5,19 @@
<p>Designed by Meu Solution</p>
<div class="tw-flex tw-justify-center tw-space-x-4 tw-mt-4">
<a href="#about" class=" hover:tw-text-blue-500">About Us</a>
<a href="#contact" class=" hover:tw-text-blue-500">Contact</a>
<a href="#privacy" class=" hover:tw-text-blue-500">Privacy Policy</a>
<div *ngFor="let item of list">
<a href="#{{item.href}}" class=" hover:tw-text-blue-500">{{item.title}}</a>
</div>
</div>
<div class="tw-flex tw-justify-center tw-space-x-4 tw-mt-4">
<div *ngFor="let item of listIcon">
<a href="#" target="_blank" class=" hover:tw-text-blue-500">
<i nz-icon nzType="facebook" class="tw-text-2xl"></i>
</a>
<a href="#" target="_blank" class=" hover:tw-text-blue-500">
<i nz-icon nzType="twitter" class="tw-text-2xl"></i>
</a>
<a href="#" target="_blank" class=" hover:tw-text-blue-500">
<i nz-icon nzType="instagram" class="tw-text-2xl"></i>
<i nz-icon nzType="{{item}}" class="tw-text-2xl"></i>
</a>
</div>
</div>
</div>
</footer>
......
......@@ -13,5 +13,12 @@ import { NzLayoutModule } from 'ng-zorro-antd/layout';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent implements OnInit {
list: {href: string, title: string}[] = [
{href: 'about', title: 'về chúng tôi'},
{href: 'contact', title: 'liên hệ'},
{href: 'privacy', title: 'chính sách bảo mật'}
];
listIcon: string[] = ['facebook', 'instagram', 'twitter'];
ngOnInit(): void {}
}
<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 { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
@Component({
selector: 'meu-admin-header',
standalone: true,
imports: [CommonModule,NzLayoutModule],
templateUrl: './header.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HeaderComponent implements OnInit {
@Input() role = '';
ngOnInit(): void {}
}
<nz-layout>
<nz-sider nzCollapsible [nzTrigger]="null">
<div class="logo"></div>
<nz-layout class="tw-h-screen tw-flex tw-flex-col">
<nz-header class="tw-flex tw-justify-between tw-items-center tw-px-4 tw-bg-[#1f487725] tw-shadow-md">
<div class="tw-flex tw-items-center">
<button
nz-button
nzType="text"
class="tw-block md:tw-hidden"
(click)="toggleSider()"
>
<span nz-icon [nzType]="isCollapsed ? 'menu-unfold' : 'menu-fold'" nzTheme="outline"></span>
</button>
<div class="tw-ml-2 tw-text-xl tw-font-bold">MeU Solution</div>
</div>
<div class="tw-hidden md:tw-flex tw-items-center">
<button nz-button nzType="default" (click)="logout()">
<span nz-icon nzType="logout" nzTheme="outline"></span>
<span class="tw-ml-2">Đăng xuất</span>
</button>
</div>
</nz-header>
<nz-layout class="tw-flex-1">
<nz-sider
nzCollapsible
[(nzCollapsed)]="isCollapsed"
[nzBreakpoint]="'lg'"
[nzWidth]="200"
[nzCollapsedWidth]="80"
class="tw-bg-gray-800"
>
<ul nz-menu nzTheme="dark" nzMode="inline">
<li nz-menu-item routerLink="/home">
Trang chủ
</li>
<li nz-menu-item routerLink="/admin/job-management/Job">
Danh sách công việc
<li nz-menu-item routerLink="/home" routerLinkActive="tw-bg-gray-700">
<span nz-icon nzType="home" nzTheme="outline"></span>
<span class="tw-ml-2">Trang chủ</span>
</li>
<li nz-menu-item (click)="logout()">
<span nz-icon nzType="logout" nzTheme="outline"></span>
Đăng xuất
<li nz-menu-item routerLink="/admin/job-management/Job" routerLinkActive="tw-bg-gray-700">
<span nz-icon nzType="unordered-list" nzTheme="outline"></span>
<span class="tw-ml-2">Danh sách công việc</span>
</li>
</ul>
</nz-sider>
<nz-layout>
<nz-content>
<nz-layout class="tw-flex-1 tw-bg-gray-100">
<nz-content >
<router-outlet></router-outlet>
</nz-content>
<meu-footer></meu-footer>
<meu-footer >
&copy; 2024 Công ty của bạn. Bảo lưu mọi quyền.
</meu-footer>
</nz-layout>
</nz-layout>
</nz-layout>
......@@ -15,7 +15,9 @@
background: rgba(255, 255, 255, 0.2);
margin: 16px;
}
::ng-deep .ant-drawer-body {
padding: 0;
}
nz-header {
background: #fff;
padding: 0;
......
......@@ -3,7 +3,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { NzLayoutModule } from 'ng-zorro-antd/layout';
import { CommonModule, DatePipe } from '@angular/common';
import { HeaderComponent } from './Components/header/header.component';
import { FooterComponent } from './Components/footer/footer.component';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
......@@ -20,20 +19,23 @@ import { AuthService } from '../../../../+login/data-access/Services/Auth.Servic
RouterLink,
CommonModule,
RouterModule,
HeaderComponent,
FooterComponent,
NzButtonModule,
NzBreadCrumbModule,
NzIconModule,
NzLayoutModule,
NzMenuModule,
NzToolTipModule,
NzIconModule,
FooterComponent
],
styleUrls: ['./layout.component.scss'],
templateUrl: './layout.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminLayoutComponent implements OnInit {
isCollapsed = false;
toggleSider(): void {
this.isCollapsed = !this.isCollapsed;
}
constructor(private authentication : AuthService) {}
ngOnInit(): void {
}
......
<nz-card *ngIf="job" [nzTitle]="job.title" [nzExtra]="extraTemplate" [nzBodyStyle]="{'padding': '8px'}">
<p>loại công việc: {{job.type}}</p>
<p>Công ty: {{job.company}}</p>
<p>Địa chỉ làm việc: {{job.location}}</p>
</nz-card>
<ng-template #extraTemplate>
<a (click)="onShowDetail()">Chi tiết</a>
</ng-template>
<nz-modal [(nzVisible)]="isModalVisible" nzTitle={{job?.title}} nzOkText="Ứng tuyển" nzCancelText="Trở về" (nzOnOk)="onOk()" (nzOnCancel)="onCancel()">
<ng-container *nzModalContent>
<p>
<strong>Chi tiết công việc</strong>
</p>
<p>
<label>Loại công việc</label>
{{ job?.type }}
</p>
<p>
<label>Ngày tạo</label>
{{ job?.created_at| date: 'dd/MM/yyyy' }}
</p>
<p>
<label>Link công ty</label>
{{ job?.company_url }}
</p>
<div>
<strong>Mô tả:</strong>
<div [innerHTML]="job?.description "></div>
</div>
</ng-container>
</nz-modal>
import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
import { NzCardModule } from "ng-zorro-antd/card";
import { Job } from "../../data-access/model/Job.model";
import { CommonModule } from "@angular/common";
import { NzModalModule } from "ng-zorro-antd/modal";
@Component({
selector: 'job-card',
templateUrl: './JobCard.component.html',
imports: [NzCardModule,
CommonModule,
NzModalModule
],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobCardComponent {
@Input() job?: Job;
isModalVisible = false;
onShowDetail() {
this.isModalVisible = true;
}
onOk() {
this.isModalVisible = false
}
onCancel(){
this.isModalVisible = false
}
}
<div *ngIf="Job; else loadingOrError">
<div class="header flex items-center space-x-4">
<img
*ngIf="Job.company_logo"
[src]="Job.company_logo"
alt="{{ Job.company }} Logo"
class="company-logo w-16 h-16 object-contain"
/>
<h2 class="tw-text-2xl tw-font-bold">{{ Job.title }}</h2>
</div>
<div class="details mt-4">
<p *ngIf="Job.type"><strong>Loại công việc:</strong> {{ Job.type }}</p>
<p *ngIf="Job.company">
<strong>Công ty:</strong>
<a>{{ Job.company }}</a
>
<span *ngIf="!Job.company_url">{{ Job.company }}</span>
</p>
<p *ngIf="Job.location"><strong>Vị trí:</strong> {{ Job.location }}</p>
<p class="tw-text-sm tw-text-gray-500">
<strong>Ngày đăng:</strong> {{ Job.created_at | date : "medium" }}
</p>
</div>
<hr class="my-4" />
<div class="description mt-6">
<p [innerHTML]="Job.description"></p>
</div>
</div>
<ng-template #loadingOrError>
<div class="status-message">
<p *ngIf="Job === null" class="error text-red-500">
Có lỗi khi tải thông tin công việc.
</p>
<p *ngIf="Job === undefined" class="loading">Đang tải...</p>
</div>
</ng-template>
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from "@angular/core";
import { Job } from "../../data-access/model/Job.model";
import { JobService } from "../../data-access/service/Job.service";
import { catchError, of, tap } from "rxjs";
import { CommonModule } from "@angular/common";
import { FormsModule } from "@angular/forms";
@Component(
{
selector: 'app-home-job-detail',
templateUrl: './JobDetail.component.html',
standalone: true,
imports: [
CommonModule,
FormsModule
],
changeDetection: ChangeDetectionStrategy.OnPush,
}
)
export class JobDetailComponent implements OnInit{
@Input() id: string = "";
Job?: Job;
constructor(private JobService: JobService, private _cdr: ChangeDetectorRef) { }
ngOnInit(): void {
this.JobService.jobGetById(this.id).pipe(
tap((data) => {
this.Job = data.responseData as Job;
this._cdr.markForCheck();
}),
catchError((error) => {
return of(null);
})
).subscribe();
}
}
<div class="tw-flex tw-justify-center tw-py-8">
<h1 class="tw-text-2xl tw-font-bold">Danh sách công việc</h1>
</div>
<div class="tw-flex tw-justify-center tw-space-x-4 tw-mb-4">
<input
nz-input
placeholder="Tìm kiếm"
class="tw-w-full tw-max-w-md"
[(ngModel)]="searchTerm"
(ngModelChange)="search()"
/>
</div>
<div class="tw-flex tw-justify-center">
<nz-list
nzItemLayout="vertical"
[nzDataSource]="displayedJobs"
class="tw-w-full tw-max-w-4xl"
nzBordered
>
<nz-list-item
*ngFor="let item of displayedJobs"
class="tw-bg-[#3174c025] tw-shadow tw-rounded tw-p-4"
>
<nz-list-item-meta>
<nz-list-item-meta-title>
<a href="/DetailJob/{{ item?.id }}" class="tw-text-base tw-font-bold">{{ item?.title }}</a>
</nz-list-item-meta-title>
</nz-list-item-meta>
<ul nz-list-item-actions class="tw-flex tw-flex-wrap tw-gap-4 tw-mt-2">
<nz-list-item-action *ngIf="item?.type">
<i nz-icon nzType="star-o" class="tw-mr-1"></i>
{{ item?.type }}
</nz-list-item-action>
<nz-list-item-action *ngIf="item?.location">
<i nz-icon nzType="environment-o" class="tw-mr-1"></i>
{{ item?.location }}
</nz-list-item-action>
<nz-list-item-action *ngIf="item?.created_at">
<i nz-icon nzType="calendar-o" class="tw-mr-1"></i>
{{ item?.created_at | date:'medium' }}
</nz-list-item-action>
</ul>
</nz-list-item>
</nz-list>
</div>
<div class="tw-flex tw-justify-center tw-my-8">
<nz-pagination
[nzPageIndex]="currentPage"
[nzPageSize]="pageSize"
[nzTotal]="filteredJobs.length"
(nzPageIndexChange)="onPageChange($event)"
[nzShowSizeChanger]="false"
>
</nz-pagination>
</div>
// JobListCard.component.ts
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
} from "@angular/core";
import { NzDividerModule } from "ng-zorro-antd/divider";
import { NzGridModule } from "ng-zorro-antd/grid";
import { JobModel } from "../../../+admin/data-access/model/jobModel.model";
import { JobService } from "../../data-access/service/Job.service";
import { tap } from "rxjs/operators";
import { CommonModule } from "@angular/common";
import { NzListModule } from "ng-zorro-antd/list";
import { NzCardModule } from "ng-zorro-antd/card";
import { NzTableModule } from "ng-zorro-antd/table";
import { NzPaginationModule } from "ng-zorro-antd/pagination";
import { FormsModule } from "@angular/forms";
import { NzInputModule } from "ng-zorro-antd/input";
import { NzButtonModule } from "ng-zorro-antd/button";
import { NzIconModule } from "ng-zorro-antd/icon";
import { RouterLink } from "@angular/router";
@Component({
selector: "app-home-list-job",
templateUrl: "./JobList.component.html",
imports: [
CommonModule,
FormsModule,
NzDividerModule,
NzGridModule,
NzListModule,
NzCardModule,
NzTableModule,
NzPaginationModule,
NzInputModule,
NzButtonModule,
NzIconModule,
],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobListCardComponent implements OnInit {
listOfJob: JobModel.Job[] = [];
filteredJobs: JobModel.Job[] = [];
displayedJobs: JobModel.Job[] = [];
searchTerm: string = "";
currentPage: number = 1;
pageSize: number = 10;
constructor(
private jobService: JobService,
private _changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
this.fetchJobs();
}
fetchJobs(): void {
this.jobService.jobsGet().pipe(
tap((res) => {
this.listOfJob = res.responseData?.rows as JobModel.Job[];
this.filteredJobs = [...this.listOfJob];
this.updateDisplayedJobs();
this._changeDetectorRef.markForCheck();
})
).subscribe();
}
search(): void {
if (this.searchTerm.trim()) {
const term = this.searchTerm.trim().toLowerCase();
this.filteredJobs = this.listOfJob.filter((job) =>
(job.title && job.title.toLowerCase().includes(term)) ||
(job.type && job.type.toLowerCase().includes(term)) ||
(job.location && job.location.toLowerCase().includes(term))
);
} else {
this.filteredJobs = [...this.listOfJob];
}
this.currentPage = 1;
this.updateDisplayedJobs();
this._changeDetectorRef.markForCheck();
}
onPageChange(page: number): void {
this.currentPage = page;
this.updateDisplayedJobs();
this._changeDetectorRef.markForCheck();
}
updateDisplayedJobs(): void {
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
this.displayedJobs = this.filteredJobs.slice(startIndex, endIndex);
}
}
<nz-list nzGrid>
<div nz-row [nzGutter]="16">
@for (item of listOfJob; track item) {
<div nz-col [nzSpan]="6">
<nz-list-item>
<button nz-button class="tw-w-full">
<job-card [job]="item"></job-card>
</button>
</nz-list-item>
</div>
}
</div>
</nz-list>
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } 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";
import { JobService } from "../../data-access/service/Job.service";
import { tap } from "rxjs";
import { CommonModule } from "@angular/common";
import { NzListModule } from 'ng-zorro-antd/list';
import { NzCardModule } from "ng-zorro-antd/card";
import { RouterLink } from "@angular/router";
@Component({
selector: 'app-home-list-job',
templateUrl: './JobListCard.component.html',
imports: [
CommonModule,
NzDividerModule,
NzGridModule,
JobCardComponent,
NzGridModule,
NzListModule,
NzCardModule,
RouterLink
],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class JobListCardComponent implements OnInit {
listOfJob?: JobModel[] ;
constructor(private jobService: JobService,private _changeDetectorRef: ChangeDetectorRef,) {}
ngOnInit() {
this.jobService.jobsGet().pipe(
tap((res) => {
this.listOfJob = res.responseData?.rows as JobModel[];
this._changeDetectorRef.markForCheck();
console.log(this.listOfJob);
}),
).subscribe();
}
}
......@@ -10,8 +10,13 @@ import { HttpClient } from "@angular/common/http";
})
export class JobService {
apiUrl = environment.API_DOMAIN + '/jobs';
apiUrlById = environment.API_DOMAIN + '/jobById';
constructor(private http: HttpClient) {}
jobsGet(): Observable<ResponseResult<Rows<Job>>> {
return this.http.get<ResponseResult<Rows<Job>>>(this.apiUrl);
}
jobGetById(id:string): Observable<ResponseResult<Job>> {
return this.http.get<ResponseResult<Job>>(`${this.apiUrlById}/${id}`);
}
}
......@@ -18,7 +18,6 @@ export class AuthService {
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);
})
);
......@@ -26,7 +25,6 @@ export class AuthService {
logout(): void {
localStorage.removeItem('token');
localStorage.removeItem('expirationTime');
localStorage.removeItem('role');
this.router.navigate(['/login']);
}
......
<div class="tw-flex tw-items-center tw-justify-center tw-min-h-screen tw-bg-gray-100">
<form nz-form [formGroup]="loginForm" class="tw-login-form tw-bg-white tw-p-8 tw-shadow-lg tw-rounded-lg tw-w-full tw-max-w-md" (ngSubmit)="onSubmit()">
<nz-form-text class="tw-text-4xl tw-font-bold tw-text-center tw-mb-6">LOGIN</nz-form-text>
<nz-form-text class="tw-text-4xl tw-font-bold tw-text-center tw-mb-6">Đăng nhập</nz-form-text>
<nz-form-item>
<nz-form-control nzErrorTip="Please input your username!">
<nz-form-control nzErrorTip="Nhập tên tài khoản!">
<nz-input-group nzPrefixIcon="user">
<input type="text" nz-input formControlName="userName" placeholder="Username" />
<input type="text" nz-input formControlName="userName" placeholder="tên tài khoản" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="Please input your Password!">
<nz-form-control nzErrorTip="Tối thiểu 6 ký tự!">
<nz-input-group nzPrefixIcon="lock">
<input type="password" nz-input formControlName="password" placeholder="Password" minlength="6" />
<input type="password" nz-input formControlName="password" placeholder="Mật khẩu" minlength="6" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<p *ngIf="MgsError" class="error-message tw-text-center">{{MgsError}}</p>
<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'">Đăng nhập</button>
</form>
</div>
......@@ -62,14 +62,11 @@ export class LoginComponent implements OnInit {
this.authService.login(this.loginForm.value.userName, this.loginForm.value.password).pipe(
catchError((err) => {
this.MgsError = err.message;
console.log(this.MgsError);
console.log(err);
return of(null);
}),
finalize(() => {
if(this.authService.isLoggedIn())
this.authService.loginSussces();
console.log('Login request completed');
if(this.authService.isLoggedIn()){
this.authService.loginSussces();}
})
).subscribe();
}
......
......@@ -24,7 +24,13 @@ export const shellRoutes: Routes = [
path: 'ListJob',
canActivate: [],
loadComponent: () =>
import('../../+home/component/JobListCard/JobListCard.component').then((m) => m.JobListCardComponent),
import('../../+home/component/JobList/JobList.component').then((m) => m.JobListCardComponent),
},
{
path: 'DetailJob/:id',
canActivate: [],
loadComponent: () =>
import('../../+home/component/JobDetail/JobDetail.component').then((m) => m.JobDetailComponent),
},
],
......
<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">
<header class="tw-bg-gray-900 tw-py-4 md:tw-px-4">
<div class="tw-container tw-mx-auto tw-flex tw-justify-between tw-items-center tw-px-4 md:tw-px-0">
<div class="tw-text-3xl tw-font-bold">
<a href="/home" class="hover:tw-text-gray-400">MeU Solution</a>
</div>
......@@ -7,12 +7,13 @@
<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="/home" class="tw-text-gray-300 hover:tw-text-white">Trang chủ</a></li>
<li><a href="/ListJob" class="tw-text-gray-300 hover:tw-text-white">Danh sách công việc</a></li>
<li><a href="/admin" class="tw-text-gray-300 hover:tw-text-white">Admin</a></li>
<ul class="tw-flex tw-space-x-8 tw-justify-center md:tw-justify-end">
<div *ngFor="let item of list">
<li><a href="/{{item.href}}" class="tw-text-gray-300 hover:tw-text-white">{{item.title}}</a></li>
</div>
<li><a (click)="logout()" class="tw-text-gray-300 hover:tw-text-white">Đăng xuất</a></li>
</ul>
</div>
</nav>
</header>
......@@ -10,6 +10,12 @@ import { AuthService } from '../../../../../+login/data-access/Services/Auth.Ser
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HeaderComponent implements OnInit {
list: {href: string, title: string}[] = [
{href: 'home', title: 'Trang chủ'},
{href: 'ListJob', title: 'Danh sách công việc'},
{href: 'admin', title: 'Trang Admin'}
];
ngOnInit(): void {}
constructor (private authentication : AuthService) {}
logout() {
......
......@@ -33,8 +33,10 @@ import { NzLayoutModule } from 'ng-zorro-antd/layout';
<nz-layout>
<meu-header></meu-header>
<div class="tw-relative tw-min-h-[92dvh]">
<div class="tw-max-w-5xl tw-mx-auto tw-py-10 tw-px-3 tw-bg-white">
<router-outlet></router-outlet>
</div>
</div>
<meu-footer></meu-footer>
</nz-layout>
</div>
......@@ -45,7 +47,7 @@ import { NzLayoutModule } from 'ng-zorro-antd/layout';
export class LayoutComponent implements OnInit {
isSmallScreen = signal(false);
constructor(private breakpointObserver: BreakpointObserver) {}
constructor(private breakpointObserver: BreakpointObserver) { }
ngOnInit() {
this.breakpointObserver.observe([Breakpoints.Handset]).subscribe((res) => {
this.isSmallScreen.set(res.matches);
......
......@@ -12,3 +12,60 @@ import { RouterOutlet } from '@angular/router';
export class AppComponent {
title = 'Meu-template-CSR';
}
// onOk(): void {
// if (this.isOnEdit && this.selectedJob) {
// if(!this.validateJobForm.valid){
// this.createNotification();
// return;}
// 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,
// location: this.validateJobForm.value.location ?? undefined,
// title: this.validateJobForm.value.title ?? undefined,
// description: this.validateJobForm.value.description ?? undefined,
// };
// this.jobService.editJob(updatedJob).pipe(
// tap((res) => {
// this.message.success(res.message, { nzDuration: 2500 });
// this._cdr.markForCheck();
// this.loadDataFromServer();
// }),
// catchError((err) => {
// this.message.error(err.message, { nzDuration: 2500 });
// return of(null);
// })
// ).subscribe();
// } else if (this.isOnAdd && this.selectedJob) {
// if(!this.validateJobForm.valid){
// this.createNotification();
// return;}
// 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,
// 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.success(res.message, { nzDuration: 2500 });
// this.loadDataFromServer();
// }),
// catchError((err) => {
// this.message.error(err.message, { nzDuration: 2500 });
// return of(null);
// })
// ).subscribe();
// }
// this.isOpenModal = false;
// this.isOnEdit = false;
// this.isOnAdd = false;
// }
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