Commit 0987f285 by Ooh-Ao

จัดการสมาชิก

parent d2625cfd
...@@ -24,15 +24,21 @@ async def create_project_member(db: AsyncSession, pm_in: ProjectMemberCreate): ...@@ -24,15 +24,21 @@ async def create_project_member(db: AsyncSession, pm_in: ProjectMemberCreate):
raise HTTPException(status_code=400, detail=str(e.orig)) raise HTTPException(status_code=400, detail=str(e.orig))
return new_pm return new_pm
# READ ALL (โหลดข้อมูลของ Project ด้วย) # READ ALL (โหลดข้อมูล Project และ Member ด้วย)
async def get_all_project_members(db: AsyncSession): async def get_all_project_members(db: AsyncSession):
stmt = select(ProjectMember).options(selectinload(ProjectMember.project)) stmt = select(ProjectMember).options(
selectinload(ProjectMember.project),
selectinload(ProjectMember.member)
)
result = await db.execute(stmt) result = await db.execute(stmt)
return result.scalars().all() return result.scalars().all()
# READ ONE (โหลดข้อมูลของ Project ด้วย) # READ ONE (โหลดข้อมูล Project และ Member ด้วย)
async def get_project_member_by_id(db: AsyncSession, pm_id: UUID): async def get_project_member_by_id(db: AsyncSession, pm_id: UUID):
stmt = select(ProjectMember).options(selectinload(ProjectMember.project)).where(ProjectMember.pmId == pm_id) stmt = select(ProjectMember).options(
selectinload(ProjectMember.project),
selectinload(ProjectMember.member)
).where(ProjectMember.pmId == pm_id)
result = await db.execute(stmt) result = await db.execute(stmt)
return result.scalar_one_or_none() return result.scalar_one_or_none()
...@@ -73,8 +79,22 @@ async def delete_project_member(db: AsyncSession, pm_id: UUID): ...@@ -73,8 +79,22 @@ async def delete_project_member(db: AsyncSession, pm_id: UUID):
return {"message": "ProjectMember deleted successfully"} return {"message": "ProjectMember deleted successfully"}
# READ Projects by MemberId (โหลดข้อมูล Project ที่สัมพันธ์กับ ProjectMember ด้วย) # READ Projects by MemberId (โหลดข้อมูล Project ด้วย)
async def get_projects_by_member_id(db: AsyncSession, member_id: UUID): async def get_projects_by_member_id(db: AsyncSession, member_id: UUID):
stmt = select(ProjectMember).options(selectinload(ProjectMember.project)).where(ProjectMember.memberId == member_id) stmt = select(ProjectMember).options(
selectinload(ProjectMember.project),
selectinload(ProjectMember.member)
).where(ProjectMember.memberId == member_id)
result = await db.execute(stmt)
return result.scalars().all()
async def get_members_by_project_id(db: AsyncSession, project_id: UUID):
"""
ดึงข้อมูล ProjectMember ทั้งหมดที่สัมพันธ์กับ project_id ที่กำหนด
พร้อมโหลดข้อมูลของ Member ด้วย (nested)
"""
stmt = select(ProjectMember).options(
selectinload(ProjectMember.member)
).where(ProjectMember.projectId == project_id)
result = await db.execute(stmt) result = await db.execute(stmt)
return result.scalars().all() return result.scalars().all()
...@@ -34,7 +34,7 @@ class Member(Base): ...@@ -34,7 +34,7 @@ class Member(Base):
updatedAt = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow) updatedAt = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow)
# ProjectEmployees = relationship("ProjectEmployee", back_populates="member") # ProjectEmployees = relationship("ProjectEmployee", back_populates="member")
project_member = relationship("ProjectMember", back_populates="member")
# ฟังก์ชันเพื่อแฮชรหัสผ่านก่อนบันทึก # ฟังก์ชันเพื่อแฮชรหัสผ่านก่อนบันทึก
def hash_password(self, password): def hash_password(self, password):
self.passwordHash = bcrypt.hash(password) self.passwordHash = bcrypt.hash(password)
......
...@@ -10,23 +10,9 @@ class ProjectMember(Base): ...@@ -10,23 +10,9 @@ class ProjectMember(Base):
__tablename__ = 'project_member' __tablename__ = 'project_member'
pmId = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) pmId = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
memberId = Column(UUID(as_uuid=True), ForeignKey("member.memberId"), nullable=False)
# Foreign Keys projectId = Column(UUID(as_uuid=True), ForeignKey("project.projectId"), nullable=False)
memberId = Column(
UUID(as_uuid=True),
ForeignKey("member.memberId"), # หรือ "users.userId" ตามตารางจริง
nullable=False
)
projectId = Column(
UUID(as_uuid=True),
ForeignKey("project.projectId"),
nullable=False
)
role_in_project = Column(String(100), nullable=True) role_in_project = Column(String(100), nullable=True)
# Relationship เชื่อมกลับไปยัง Project project = relationship("Project", back_populates="project_member", lazy="joined")
project = relationship("Project", back_populates="project_member") member = relationship("Member", back_populates="project_member", lazy="joined")
# ถ้ามี Model Member/User ก็สามารถประกาศ relationship ได้ เช่น
# member = relationship("Member", back_populates="project_members")
...@@ -12,7 +12,8 @@ from ..controllers.project_member_controller import ( ...@@ -12,7 +12,8 @@ from ..controllers.project_member_controller import (
get_project_member_by_id, get_project_member_by_id,
update_project_member, update_project_member,
delete_project_member, delete_project_member,
get_projects_by_member_id get_projects_by_member_id,
get_members_by_project_id
) )
from ..schemas.project_member_schema import ( from ..schemas.project_member_schema import (
ProjectMemberCreate, ProjectMemberCreate,
...@@ -75,3 +76,11 @@ async def get_projects_for_member( ...@@ -75,3 +76,11 @@ async def get_projects_for_member(
""" """
pms = await get_projects_by_member_id(db, memberId) pms = await get_projects_by_member_id(db, memberId)
return pms return pms
@router.get("/project/{projectId}", response_model=List[ProjectMemberResponse])
async def get_member_for_project(
projectId: UUID,
db: AsyncSession = Depends(get_db)
):
pms = await get_members_by_project_id(db, projectId)
return pms
\ No newline at end of file
# myproject/schemas/project_member_schema.py
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
from .project_schema import ProjectResponse # Import schema ของ Project from .project_schema import ProjectResponse # Schema ของ Project
from .member_schema import MemberResponse # Schema ของ Member
class ProjectMemberBase(BaseModel): class ProjectMemberBase(BaseModel):
memberId: UUID memberId: UUID
...@@ -15,7 +14,8 @@ class ProjectMemberCreate(ProjectMemberBase): ...@@ -15,7 +14,8 @@ class ProjectMemberCreate(ProjectMemberBase):
class ProjectMemberResponse(ProjectMemberBase): class ProjectMemberResponse(ProjectMemberBase):
pmId: UUID pmId: UUID
projectId: UUID projectId: UUID
project: Optional[ProjectResponse] = None # เพิ่ม field นี้เพื่อแสดงข้อมูลของ Project project: Optional[ProjectResponse] = None # ข้อมูลของ Project ที่สัมพันธ์กัน
member: Optional[MemberResponse] = None # ข้อมูลของ Member ที่สัมพันธ์กัน
class Config: class Config:
orm_mode = True orm_mode = True
<app-page-header [title]="'จัดการสมาชิก'" [activeTitle]="'ผู้ดูแลระบบ'" [title1]="'จัดการสมาชิก'"></app-page-header> <app-page-header [title]="'จัดการสมาชิกในโครงการ'" [activeTitle]="'จัดการข้อมูลโครงการ'"
[title1]="'จัดการสมาชิกในโครงการ'"></app-page-header>
<div class="grid grid-cols-12 gap-6"> <div class="grid grid-cols-12 gap-6">
...@@ -11,27 +12,17 @@ ...@@ -11,27 +12,17 @@
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-primary-full me-2" (click)="new()" <a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-primary-full me-2" (click)="new()"
data-hs-overlay="#modal-detail"><i class="ri-add-line font-semibold align-middle"></i>{{ 'Create' | data-hs-overlay="#modal-detail"><i class="ri-add-line font-semibold align-middle"></i>{{
'เพิ่มพนักงานเข้าโครงการ' |
translate}} translate}}
</a> </a>
<a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-success-full me-2" *ngIf="someSelected"
(click)="adjustSelect(1)"><i class="ri-user-follow-line font-semibold align-middle"></i>{{ 'Active' |
translate}}
</a>
<a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-secondary-full me-2"
*ngIf="someSelected" (click)="adjustSelect(0)"><i
class="ri-user-unfollow-line font-semibold align-middle"></i>{{ 'Unactive' |
translate}}
</a>
<a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-danger-full me-2" *ngIf="someSelected" <a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-danger-full me-2" *ngIf="someSelected"
(click)="deleteSelect()"><i class="ri-delete-bin-line font-semibold align-middle"></i>{{ 'Delete' | (click)="deleteSelect()"><i class="ri-delete-bin-line font-semibold align-middle"></i>{{ 'Delete' |
translate}} translate}}
</a> </a>
<div> <div>
<input class="form-control form-control" type="text" placeholder="ค้นหาสมาชิก" <input class="form-control form-control" type="text" placeholder="ค้นหาพนักงานในโครงการ"
aria-label=".form-control-sm example" [(ngModel)]='searchTerm'> aria-label=".form-control-sm example" [(ngModel)]='searchTerm'>
</div> </div>
<!-- <div> <!-- <div>
...@@ -56,13 +47,10 @@ ...@@ -56,13 +47,10 @@
<input class="form-check-input check-all" type="checkbox" id="all-products" <input class="form-check-input check-all" type="checkbox" id="all-products"
(change)="toggleAll($event)" [checked]="allSelected" aria-label="..."> (change)="toggleAll($event)" [checked]="allSelected" aria-label="...">
</th> </th>
<th scope="col" class="text-start">{{ 'Username' | translate}}</th> <!-- <th scope="col" class="text-start">{{ 'Username' | translate}}</th> -->
<th scope="col" class="text-start">{{ 'Fullname' | translate}}</th> <th scope="col" class="text-start">{{ 'Fullname' | translate}}</th>
<th scope="col" class="text-start">{{ 'Email' | translate}}</th> <th scope="col" class="text-start">{{ 'Email' | translate}}</th>
<th scope="col" class="text-start">{{ 'Mobile' | translate}}</th> <th scope="col" class="text-start">{{ 'Mobile' | translate}}</th>
<th scope="col" class="text-start">{{ 'User Group' | translate}}</th>
<th scope="col" class="text-start">{{ 'Status' | translate}}</th>
<th scope="col" class="text-start">{{ 'Update Date' | translate}}</th>
<th scope="col" class="text-start"></th> <th scope="col" class="text-start"></th>
</tr> </tr>
</thead> </thead>
...@@ -70,19 +58,18 @@ ...@@ -70,19 +58,18 @@
@for(item of filterList;track filterList){ @for(item of filterList;track filterList){
<tr class="border border-defaultborder dark:border-defaultborder/10"> <tr class="border border-defaultborder dark:border-defaultborder/10">
<td class="product-checkbox"><input class="form-check-input" type="checkbox" [checked]="selectedItems.get(item.memberId)" <td class="product-checkbox"><input class="form-check-input" type="checkbox"
(change)="onCheckboxChange(item.memberId)" aria-label="..." value=""> [checked]="selectedItems.get(item.memberId)" (change)="onCheckboxChange(item.memberId)"
aria-label="..." value="">
</td> </td>
<td> <td>
<div class="flex items-center"> <div class="flex items-center">
<span class="avatar avatar-sm p-1 me-1 bg-light !rounded-full"> <span class="avatar avatar-sm p-1 me-1 bg-light !rounded-full">
<img <img [src]="item.member.getPicture()" alt="" id="profile-img">
[src]="item.getPicture()"
alt="" id="profile-img">
</span> </span>
<div class="ms-2"> <div class="ms-2">
<p class="font-semibold mb-0 flex items-center text-primary"><a (click)="view(item)"> <p class="font-semibold mb-0 flex items-center text-primary"><a (click)="view(item)">
{{item.username}}</a></p> {{item.member.getFullname()}}</a></p>
<p class="text-[0.75rem] text-muted mb-0">{{item.memberId}}</p> <p class="text-[0.75rem] text-muted mb-0">{{item.memberId}}</p>
</div> </div>
</div> </div>
...@@ -98,34 +85,22 @@ ...@@ -98,34 +85,22 @@
</div> </div>
</div> </div>
</td> --> </td> -->
<td> {{item.firstName}} {{item.lastName}}</td> <!-- <td> {{item.member.getFullname()}}</td> -->
<td> <td>
<div> <div>
<span class="block mb-1"><i <span class="block mb-1"><i
class="ri-mail-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.email}}</span> class="ri-mail-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.member.email}}</span>
</div> </div>
</td> </td>
<td> <td>
<div> <div>
<span class="block"><i <span class="block"><i
class="ri-phone-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.phoneNumber}}</span> class="ri-phone-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.member.phoneNumber}}</span>
</div> </div>
</td> </td>
<td> <span
class="badge bg-{{ item.role == 1 ? 'primary' : 'info'}} text-white">{{item.getRole()}}</span>
</td>
<td> <span
class="badge bg-{{ item.status == 1 ? 'primary' : 'danger'}} text-white">{{item.getStatus()}}</span>
</td>
<td><span class="badge bg-info/10 text-primary"><i
class="bi bi-clock me-1"></i>{{item.updatedAt | date : 'medium'}}</span></td>
<td>
<td> <td>
<div class="flex flex-row items-center !gap-2 "> <div class="flex flex-row items-center !gap-2 ">
<a aria-label="anchor" (click)="view(item)" data-hs-overlay="#modal-detail"
class="ti-btn ti-btn-wave !gap-0 !m-0 bg-info/10 text-info hover:bg-info hover:text-white hover:border-info"><i
class="ri-pencil-line"></i></a>
<a aria-label="anchor" href="javascript:void(0);" (click)="delete(item)" <a aria-label="anchor" href="javascript:void(0);" (click)="delete(item)"
class="ti-btn ti-btn-wave product-btn !gap-0 !m-0 bg-danger/10 text-danger hover:bg-danger hover:text-white hover:border-danger"><i class="ti-btn ti-btn-wave product-btn !gap-0 !m-0 bg-danger/10 text-danger hover:bg-danger hover:text-white hover:border-danger"><i
class="ri-delete-bin-line"></i></a> class="ri-delete-bin-line"></i></a>
...@@ -186,11 +161,11 @@ ...@@ -186,11 +161,11 @@
<!-- Start:: Create Contact --> <!-- Start:: Create Contact -->
<div id="modal-detail" class="hs-overlay hidden ti-modal [--overlay-backdrop:static]"> <div id="modal-detail" class="hs-overlay hidden ti-modal [--overlay-backdrop:static]">
<div class="hs-overlay-open:mt-7 ti-modal-box mt-0 ease-out"> <div class="hs-overlay-open:mt-7 ti-modal-box mt-0 ease-out lg:!max-w-4xl lg:w-full m-3 lg:!mx-auto">
<div class="ti-modal-content"> <div class="ti-modal-content">
<div class="ti-modal-header"> <div class="ti-modal-header">
<h6 class="modal-title text-[1rem] font-semibold text-defaulttextcolor" id="mail-ComposeLabel">{{ 'Create' | <h6 class="modal-title text-[1rem] font-semibold text-defaulttextcolor" id="mail-ComposeLabel">{{
translate }} {{ 'User' | translate }} 'เพิ่มพนักงานเข้าโครงการ' }}
</h6> </h6>
<button type="button" class="hs-dropdown-toggle !text-[1rem] !font-semibold !text-defaulttextcolor" <button type="button" class="hs-dropdown-toggle !text-[1rem] !font-semibold !text-defaulttextcolor"
data-hs-overlay="#modal-detail" #closeModal> data-hs-overlay="#modal-detail" #closeModal>
...@@ -199,100 +174,133 @@ ...@@ -199,100 +174,133 @@
</button> </button>
</div> </div>
<div class="ti-modal-body px-4"> <div class="ti-modal-body px-4">
<div class="grid grid-cols-12 gap-4"> <div class="grid grid-cols-12 gap-6">
<div class="xl:col-span-12 col-span-12"> <div class="xl:col-span-12 col-span-12">
<div class="mb-0 text-center"> <div class="box">
<span class="avatar avatar-xxl avatar-rounded"> <div class="box-header justify-between">
<img [src]="selectModel.getPicture()" alt="" id="profile-img"> <div class="box-title">
<span class="badge rounded-full bg-primary avatar-badge"> {{ 'All List' | translate}} <span
<input ng2FileSelect [uploader]="uploaderProfile" type="file" name="photo" class="absolute w-full h-full opacity-[0]" id="profile-change"> class="badge bg-light text-default rounded-full ms-1 text-[0.75rem] align-middle">{{itemsList.length}}</span>
<i class="fe fe-camera text-[.625rem]"></i>
</span>
</span>
</div>
</div> </div>
<div class="xl:col-span-12 col-span-12">
<label for="deal-title" class="form-label">{{'Username' | translate}}</label>
<input type="text" class="form-control" id="deal-title" placeholder="" [(ngModel)]="selectModel.username">
<div class="text-danger" *ngIf="!selectModel.username">
{{'Please fill in information' | translate}}
</div>
</div> </div>
<div class="box-body">
<div class="table-responsive">
<table class="table whitespace-nowrap min-w-full ti-custom-table-hover">
<thead>
<tr class="border-b border-defaultborder">
<th scope="col" class="text-start"></th>
<th scope="col" class="text-start">{{ 'Fullname' | translate}}</th>
<th scope="col" class="text-start">{{ 'Email' | translate}}</th>
<th scope="col" class="text-start">{{ 'Mobile' | translate}}</th>
<div class="xl:col-span-12 col-span-12"> </tr>
<label for="deal-title" class="form-label">{{'Password' | translate}}</label> </thead>
<input type="password" class="form-control" id="deal-title" placeholder="" <tbody>
[(ngModel)]="selectModel.password"> @for(item of userList;track userList){
<div class="text-danger" *ngIf="!selectModel.password">
{{'Please fill in information' | translate}} <tr class="border border-defaultborder dark:border-defaultborder/10">
<td>
<!-- <div class="flex flex-row items-center !gap-2 ">
<a aria-label="anchor" (click)="view(item)" data-hs-overlay="#modal-detail"
class="ti-btn ti-btn-wave !gap-0 !m-0 bg-info/10 text-info hover:bg-info hover:text-white hover:border-info"><i
class="ri-pencil-line"></i></a>
<a aria-label="anchor" href="javascript:void(0);" (click)="delete(item)"
class="ti-btn ti-btn-wave product-btn !gap-0 !m-0 bg-danger/10 text-danger hover:bg-danger hover:text-white hover:border-danger"><i
class="ri-delete-bin-line"></i></a>
</div> -->
<a href="javascript:void(0);" class="hs-dropdown-toggle ti-btn ti-btn-primary-full me-2"
(click)="selectMember(item)"><i
class="ri-add-line font-semibold align-middle"></i>{{ 'เลือก' |
translate}}
</a>
</td>
<td>
<div class="flex items-center">
<span class="avatar avatar-sm p-1 me-1 bg-light !rounded-full">
<img [src]="item.getPicture()" alt="" id="profile-img">
</span>
<div class="ms-2">
<p class="font-semibold mb-0 flex items-center text-primary"><a>
{{item.firstName}} {{item.lastName}}</a></p>
<p class="text-[0.75rem] text-muted mb-0">{{item.memberId}}</p>
</div> </div>
</div> </div>
</td>
<div class="xl:col-span-12 col-span-12"> <!-- <td>
<label for="deal-title" class="form-label">{{'Confirm Password' | translate}}</label> <div class="flex">
<input type="password" class="form-control" id="deal-title" placeholder="" [(ngModel)]="confirmPassword"> <div class="ms-2">
<div class="text-danger" *ngIf="!confirmPassword"> <p class="font-semibold mb-0 flex items-center text-primary"><a (click)="view(item)"
{{'Please fill in information' | translate}} data-hs-overlay="#modal-detail">
</div> {{item.username}}</a></p>
<div class="text-danger" *ngIf="confirmPassword && (confirmPassword != selectModel.password)"> <p class="text-[0.75rem] text-muted mb-0">{{item.memberId}}</p>
{{'Password Not Match' | translate}}
</div> </div>
</div> </div>
</td> -->
<div class="xl:col-span-6 col-span-12"> <!-- <td> {{item.firstName}} {{item.lastName}}</td> -->
<label for="deal-title" class="form-label">{{'ชื่อ' | translate}}</label> <td>
<input type="text" class="form-control" id="deal-title" placeholder="" [(ngModel)]="selectModel.firstName"> <div>
<div class="text-danger" *ngIf="!selectModel.firstName"> <span class="block mb-1"><i
{{'Please fill in information' | translate}} class="ri-mail-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.email}}</span>
</div> </div>
</td>
<td>
<div>
<span class="block"><i
class="ri-phone-line me-2 align-middle text-[.875rem] text-[#8c9097] dark:text-white/50 inline-flex"></i>{{item.phoneNumber}}</span>
</div> </div>
</td>
</tr>
}
<div class="xl:col-span-6 col-span-12"> </tbody>
<label for="deal-title" class="form-label">{{'นามสกุล' | translate}}</label> </table>
<input type="text" class="form-control" id="deal-title" placeholder="" [(ngModel)]="selectModel.lastName">
<div class="text-danger" *ngIf="!selectModel.lastName">
{{'Please fill in information' | translate}}
</div> </div>
</div> </div>
<!-- <div class="box-footer">
<div class="xl:col-span-6 col-span-12"> <div class="flex items-center flex-wrap overflow-auto" *ngIf="filterList.length > 0">
<label for="deal-title" class="form-label">{{'อีเมล' | translate}}</label> <div class="mb-2 sm:mb-0">
<input type="text" class="form-control" id="deal-title" placeholder="" [(ngModel)]="selectModel.email"> <div>
<div class="text-danger" *ngIf="!selectModel.email"> {{'Showing' | translate}} {{filterList.length}} {{'entries'
{{'Please fill in information' | translate}} | translate}} <i class="bi bi-arrow-right ms-2 font-semibold"></i>
</div> </div>
</div> </div>
<div class="ms-auto">
<nav aria-label="Page navigation">
<ul class="ti-pagination mb-0">
<li *ngIf="pageIndex>0" class="page-item {{pageIndex==0 ? 'disabled' : ''}}"><a
class="page-link px-3 py-[0.375rem]"
(click)="pageIndex = pageIndex-1;updatePagedItems()">{{'Previous' | translate}}</a></li>
<li class="page-item"><a class="page-link px-3 py-[0.375rem]" href="javascript:void(0);"
*ngIf="pageIndex-1>0" (click)="pageIndex = pageIndex-2;updatePagedItems()">{{pageIndex-1}}</a></li>
<li class="page-item"><a class="page-link px-3 py-[0.375rem]" href="javascript:void(0);"
*ngIf="pageIndex>0 && ((pageIndex-1)*10 < (searchTerm == '' ? itemsList.length : filterList.length))"
(click)="pageIndex = pageIndex-1;updatePagedItems()">{{pageIndex}}</a></li>
<li class="page-item"><a class="page-link active px-3 py-[0.375rem]"
<div class="xl:col-span-6 col-span-12"> href="javascript:void(0);">{{pageIndex +1}}</a>
<label for="deal-title" class="form-label">{{'เบอร์ติดต่อ' | translate}}</label> </li>
<input type="text" class="form-control" id="deal-title" placeholder="" <li class="page-item"><a class="page-link px-3 py-[0.375rem]" href="javascript:void(0);"
[(ngModel)]="selectModel.phoneNumber"> *ngIf="(pageIndex+1)*10 < (searchTerm == '' ? itemsList.length : filterList.length)"
<div class="text-danger" *ngIf="!selectModel.phoneNumber"> (click)="pageIndex = pageIndex+1;updatePagedItems()">{{pageIndex +2}}</a></li>
{{'Please fill in information' | translate}} <li class="page-item"><a class="page-link px-3 py-[0.375rem]" href="javascript:void(0);"
*ngIf="(pageIndex+2)*10 < (searchTerm == '' ? itemsList.length : filterList.length)"
(click)="pageIndex = pageIndex+2;updatePagedItems()">{{pageIndex +3}}</a></li>
<li *ngIf="(pageIndex+1)*10 < (searchTerm == '' ? itemsList.length : filterList.length)"
class="page-item"><a class="page-link px-3 py-[0.375rem]"
(click)="pageIndex = pageIndex+1;updatePagedItems()">{{'Next' |
translate}}</a>
</li>
</ul>
</nav>
</div> </div>
</div> </div>
</div> -->
<div class="xl:col-span-12 col-span-12">
<label class="form-label">{{'User Role' | translate}}</label>
<ng-select name="choices-multiple-remove-button1" id="choices-multiple-remove-button1" placeholder=""
[(ngModel)]="selectModel.role">
<ng-option [value]="0">{{'ผู้ใช้งานทั่วไป' | translate}}</ng-option>
<ng-option [value]="1">{{'ผู้ดูแลบริษัท' | translate}}</ng-option>
</ng-select>
</div> </div>
<div class="xl:col-span-12 col-span-12">
<label class="form-label">{{'Status' | translate}}</label>
<ng-select name="choices-multiple-remove-button2" id="choices-multiple-remove-button2" placeholder=""
[(ngModel)]="selectModel.status">
<ng-option [value]="0">{{'Unactive' | translate}}</ng-option>
<ng-option [value]="1">{{'Active' | translate}}</ng-option>
</ng-select>
</div> </div>
</div> </div>
</div> </div>
<div class="ti-modal-footer"> <div class="ti-modal-footer">
...@@ -300,8 +308,8 @@ ...@@ -300,8 +308,8 @@
data-hs-overlay="#modal-detail"> data-hs-overlay="#modal-detail">
{{'Cancel' | translate}} {{'Cancel' | translate}}
</button> </button>
<button type="button" (click)="save()" class="ti-btn bg-primary text-white !font-medium">{{'Save' | <!-- <button type="button" (click)="save()" class="ti-btn bg-primary text-white !font-medium">{{'Save' |
translate}}</button> translate}}</button> -->
</div> </div>
</div> </div>
</div> </div>
......
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { ChangeDetectionStrategy, Component, ElementRef, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef, ViewChild } from '@angular/core';
import { NgSelectModule } from "@ng-select/ng-select"; import { NgSelectModule } from "@ng-select/ng-select";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { SharedModule } from "../../../shared/shared.module"; import { SharedModule } from "../../../shared/shared.module";
import { UserService } from "../../services/user.service"; import { FormsModule } from "@angular/forms";
import { UserProfileModel } from "../../models/user.model"; import swal from 'sweetalert';
import { FormsModule } from "@angular/forms"; import { MatPaginator, PageEvent } from "@angular/material/paginator";
import swal from 'sweetalert'; import { UserRoleModel } from "../../models/user-role-model";
import { MatPaginator, PageEvent } from "@angular/material/paginator"; import { FileUploadModule } from 'ng2-file-upload';
import { UserRoleModel } from "../../models/user-role-model"; import { FileItem, FileUploader, ParsedResponseHeaders } from "ng2-file-upload";
import { FileUploadModule } from 'ng2-file-upload'; import { environment } from "../../../../environments/environment";
import { FileItem, FileUploader, ParsedResponseHeaders } from "ng2-file-upload"; import { TokenService } from "../../../shared/services/token.service";
import { environment } from "../../../../environments/environment"; import { ProjectMemberService } from "../../services/project-members.service";
import { TokenService } from "../../../shared/services/token.service"; import { ProjectMemberModel } from "../../models/project-members";
import { UserService } from "../../services/user.service";
import { UserProfileModel } from "../../models/user.model";
@Component({ @Component({
selector: 'app-admin-project-emp-manage', selector: 'app-admin-project-emp-manage',
standalone: true, standalone: true,
...@@ -33,16 +35,18 @@ export class AdminProjectEmpManageComponent { ...@@ -33,16 +35,18 @@ export class AdminProjectEmpManageComponent {
allSelected = false; allSelected = false;
someSelected = false; someSelected = false;
confirmPassword = "" confirmPassword = ""
itemsList: UserProfileModel[] = [] itemsList: ProjectMemberModel[] = []
filterList: UserProfileModel[] = [] filterList: ProjectMemberModel[] = []
selectModel: UserProfileModel = new UserProfileModel() selectModel: ProjectMemberModel = new ProjectMemberModel()
selectedItems = new Map<string, boolean>(); selectedItems = new Map<string, boolean>();
roleList: UserRoleModel[] = [] roleList: UserRoleModel[] = []
empList: UserProfileModel[] = [] empList: ProjectMemberModel[] = []
descName = 'engName' descName = 'engName'
pageIndex = 0; pageIndex = 0;
uploaderProfile: FileUploader | undefined; uploaderProfile: FileUploader | undefined;
uploadErrorMsg: string = ""; uploadErrorMsg: string = "";
userList: UserProfileModel[] = []
get searchTerm(): string { get searchTerm(): string {
return this._searchTerm; return this._searchTerm;
} }
...@@ -56,12 +60,21 @@ export class AdminProjectEmpManageComponent { ...@@ -56,12 +60,21 @@ export class AdminProjectEmpManageComponent {
this.updatePagedItems() this.updatePagedItems()
} }
} }
projectId = ""
_searchTerm = ""; _searchTerm = "";
constructor(private userService: UserService, public translate: TranslateService, private tokenService: TokenService) { constructor(private projectMemberService: ProjectMemberService, private userService: UserService, public translate: TranslateService, private tokenService: TokenService) {
this.projectId = this.tokenService.getSelectCompany().projectId!;
this.getUserProject()
this.getMemberAll()
this.uploadConfig() this.uploadConfig()
} }
getMemberAll() {
this.userService.getLists().subscribe(result => {
this.userList = result
})
}
uploadConfig() { uploadConfig() {
this.uploaderProfile = new FileUploader({ this.uploaderProfile = new FileUploader({
url: environment.baseUrl + "/api/upload-image", url: environment.baseUrl + "/api/upload-image",
...@@ -103,7 +116,7 @@ export class AdminProjectEmpManageComponent { ...@@ -103,7 +116,7 @@ export class AdminProjectEmpManageComponent {
if (item.isSuccess) { if (item.isSuccess) {
const res = JSON.parse(response); const res = JSON.parse(response);
console.log("res", res); console.log("res", res);
this.selectModel.picture = res.filename this.selectModel.member.picture = res.filename
swal(res.message, "บันทึกสำเร็จ", "success"); swal(res.message, "บันทึกสำเร็จ", "success");
} else { } else {
...@@ -115,7 +128,14 @@ export class AdminProjectEmpManageComponent { ...@@ -115,7 +128,14 @@ export class AdminProjectEmpManageComponent {
ngOnInit(): void { ngOnInit(): void {
this.userService.getLists().subscribe(result => { // this.projectMemberService.getLists(this.projectId).subscribe(result => {
// this.itemsList = result
// this.updatePagedItems()
// })
}
getUserProject() {
this.projectMemberService.getLists(this.projectId).subscribe(result => {
this.itemsList = result this.itemsList = result
this.updatePagedItems() this.updatePagedItems()
}) })
...@@ -124,17 +144,17 @@ export class AdminProjectEmpManageComponent { ...@@ -124,17 +144,17 @@ export class AdminProjectEmpManageComponent {
filter(v: string) { filter(v: string) {
return this.itemsList?.filter( return this.itemsList?.filter(
(x) => (x) =>
x.memberId?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.memberId?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.username?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.username?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.email?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.email?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.phoneNumber?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.phoneNumber?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.getRole()?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.getRole()?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.getStatus()?.toLowerCase().indexOf(v.toLowerCase()) !== -1 || x.member.getStatus()?.toLowerCase().indexOf(v.toLowerCase()) !== -1 ||
x.getFullname()?.toLowerCase().indexOf(v.toLowerCase()) !== -1 x.member.getFullname()?.toLowerCase().indexOf(v.toLowerCase()) !== -1
); );
} }
delete(item: UserProfileModel) { delete(item: ProjectMemberModel) {
swal({ swal({
title: "Are you sure?", title: "Are you sure?",
text: "You won't be able to revert this!", text: "You won't be able to revert this!",
...@@ -145,9 +165,9 @@ export class AdminProjectEmpManageComponent { ...@@ -145,9 +165,9 @@ export class AdminProjectEmpManageComponent {
}) })
.then((willDelete: any) => { .then((willDelete: any) => {
if (willDelete) { if (willDelete) {
this.userService.delete(item).subscribe(result => { this.projectMemberService.delete(item).subscribe(result => {
swal("Save Success!!", "บันทึกข้อมูลสำเร็จ", "success"); swal("Save Success!!", "บันทึกข้อมูลสำเร็จ", "success");
this.ngOnInit() this.getUserProject()
}) })
} }
...@@ -156,18 +176,18 @@ export class AdminProjectEmpManageComponent { ...@@ -156,18 +176,18 @@ export class AdminProjectEmpManageComponent {
new() { new() {
this.action = 'add' this.action = 'add'
this.selectModel = new UserProfileModel() this.selectModel = new ProjectMemberModel()
} }
view(item: UserProfileModel) { view(item: ProjectMemberModel) {
this.action = 'edit' this.action = 'edit'
this.confirmPassword = '' this.confirmPassword = ''
this.selectModel = new UserProfileModel(item) this.selectModel = new ProjectMemberModel(item)
console.log(this.selectModel) console.log(this.selectModel)
} }
save() { selectMember(item: UserProfileModel) {
swal({ swal({
title: "Are you sure?", title: "Are you sure?",
text: "คุณต้องการบันทึกหรือไม่", text: "คุณต้องการบันทึกหรือไม่",
...@@ -178,17 +198,21 @@ export class AdminProjectEmpManageComponent { ...@@ -178,17 +198,21 @@ export class AdminProjectEmpManageComponent {
.then((willDelete: any) => { .then((willDelete: any) => {
if (willDelete) { if (willDelete) {
if (this.action == 'add') { if (this.action == 'add') {
this.userService.save(this.selectModel).subscribe(result => { this.projectMemberService.save({
"memberId": item.memberId,
"role_in_project": "employee",
"projectId": this.projectId
}).subscribe(result => {
console.log(result) console.log(result)
swal("Save Success!!", "บันทึกข้อมูลสมาชิก", "success"); swal("Save Success!!", "บันทึกข้อมูลสมาชิก", "success");
this.ngOnInit() this.getUserProject()
this.childModal?.nativeElement.click() this.childModal?.nativeElement.click()
}) })
} else if (this.action == 'edit') { } else if (this.action == 'edit') {
this.userService.update(this.selectModel).subscribe(result => { this.projectMemberService.update(this.selectModel).subscribe(result => {
console.log(result) console.log(result)
swal("Update Success!!", "บันทึกข้อมูลสมาชิก", "success"); swal("Update Success!!", "บันทึกข้อมูลสมาชิก", "success");
this.ngOnInit() this.getUserProject()
this.childModal?.nativeElement.click() this.childModal?.nativeElement.click()
}) })
} }
...@@ -228,7 +252,7 @@ export class AdminProjectEmpManageComponent { ...@@ -228,7 +252,7 @@ export class AdminProjectEmpManageComponent {
if (isSelected) { if (isSelected) {
const user = this.itemsList.find(user => user.memberId === memberId); const user = this.itemsList.find(user => user.memberId === memberId);
if (user) { if (user) {
employeeInfo += `${this.translate.instant('Fullname')}: ${user.getFullname()}\n`; employeeInfo += `${this.translate.instant('Fullname')}: ${user.member.getFullname()}\n`;
} }
} }
}); });
...@@ -246,9 +270,9 @@ export class AdminProjectEmpManageComponent { ...@@ -246,9 +270,9 @@ export class AdminProjectEmpManageComponent {
if (isSelected) { if (isSelected) {
const user = this.itemsList.find(user => user.memberId === memberId); const user = this.itemsList.find(user => user.memberId === memberId);
if (user) { if (user) {
this.userService.delete(user).subscribe(result => { this.projectMemberService.delete(user).subscribe(result => {
swal("Save Success!!", "บันทึกข้อมูลสำเร็จ", "success"); swal("Save Success!!", "บันทึกข้อมูลสำเร็จ", "success");
this.ngOnInit(); this.getUserProject();
}); });
} }
} }
...@@ -257,45 +281,13 @@ export class AdminProjectEmpManageComponent { ...@@ -257,45 +281,13 @@ export class AdminProjectEmpManageComponent {
}); });
} }
adjustSelect(status: number) {
let title = "Are you sure?"
let employeeInfo = ''; // ตัวแปรสำหรับเก็บข้อมูลพนักงาน
this.selectedItems.forEach((isSelected, memberId) => {
if (isSelected) {
const user = this.itemsList.find(user => user.memberId === memberId);
if (user) {
employeeInfo += `${this.translate.instant('Fullname')}: ${user.getFullname()}\n`;
}
}
});
swal({
title: title,
text: employeeInfo,
icon: "warning",
dangerMode: false,
buttons: ["Cancel", "Confirm"],
})
.then((willDelete: any) => {
if (willDelete) {
this.selectedItems.forEach((isSelected, memberId) => {
if (isSelected) {
const user = this.itemsList.find(user => user.memberId === memberId);
if (user) {
user.status = status
this.userService.update(user).subscribe(result => {
swal("Save Success!!", "บันทึกข้อมูลสำเร็จ", "success");
this.ngOnInit();
});
}
}
});
}
});
}
filterEmp(empId: string) { filterEmp(empId: string) {
this.selectModel = this.empList.filter(e => e.memberId == empId)[0] this.selectModel = this.empList.filter(e => e.memberId == empId)[0]
} }
// selectMember(){
// }
} }
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { BaseModel } from "./base.model"; import { BaseModel } from "./base.model";
import { ProjectModel } from "./project.model";
import { UserProfileModel } from "./user.model";
export class ProjectMemberModel extends BaseModel { export class ProjectMemberModel extends BaseModel {
pm_id: string; memberId: string;
user_id: string;
projectId: string;
role_in_project: string; role_in_project: string;
created_at: string; pmId: string;
updated_at: string; projectId: string;
project: ProjectModel;
member: UserProfileModel;
constructor(data?: Partial<ProjectMemberModel>, translateService?: TranslateService) { constructor(data?: Partial<ProjectMemberModel>, translateService?: TranslateService) {
super(data, translateService); super(data, translateService);
this.pm_id = data?.pm_id ?? ''; this.pmId = data?.pmId ?? '';
this.user_id = data?.user_id ?? ''; this.memberId = data?.memberId ?? '';
this.projectId = data?.projectId ?? ''; this.projectId = data?.projectId ?? '';
this.role_in_project = data?.role_in_project ?? ''; this.role_in_project = data?.role_in_project ?? '';
this.created_at = data?.created_at ?? new Date().toISOString();
this.updated_at = data?.updated_at ?? new Date().toISOString(); this.project = data?.project || new ProjectModel();
} this.member = data?.member ? new UserProfileModel(data.member) : new UserProfileModel();
}
} }
...@@ -19,7 +19,7 @@ export class ProjectMemberService { ...@@ -19,7 +19,7 @@ export class ProjectMemberService {
.pipe(map((e) => new ProjectMemberModel(e))); .pipe(map((e) => new ProjectMemberModel(e)));
} }
getLists() { getLists(projectId: string) {
return this.http return this.http
.get<ProjectMemberModel[]>(this.apiBaseUrl) .get<ProjectMemberModel[]>(this.apiBaseUrl)
.pipe( .pipe(
...@@ -27,25 +27,23 @@ export class ProjectMemberService { ...@@ -27,25 +27,23 @@ export class ProjectMemberService {
); );
} }
save(body: ProjectMemberModel) {
return this.http.post<{ save(body: any) {
"message": string, return this.http.post<ProjectMemberModel>(this.apiBaseUrl, body);
"user": ProjectMemberModel
}>(this.apiBaseUrl, new ProjectMemberModel(body));
} }
update(body: ProjectMemberModel) { update(body: ProjectMemberModel) {
return this.http.put<{ return this.http.put<{
"message": string, "message": string,
"user": ProjectMemberModel "user": ProjectMemberModel
}>(this.apiBaseUrl + "/" + body.user_id, new ProjectMemberModel(body)); }>(this.apiBaseUrl + "/" + body.memberId, new ProjectMemberModel(body));
} }
delete(body: ProjectMemberModel) { delete(body: ProjectMemberModel) {
return this.http.delete<{ return this.http.delete<{
"message": string, "message": string,
"user": ProjectMemberModel "user": ProjectMemberModel
}>(this.apiBaseUrl + "/" + body.user_id); }>(this.apiBaseUrl + "/" + body.pmId);
} }
getCompanyAdmin(memberId: string) { getCompanyAdmin(memberId: string) {
......
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