Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/manage user #42

Open
wants to merge 14 commits into
base: dev
Choose a base branch
from
24 changes: 24 additions & 0 deletions src/lib/api/getAll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export async function getAllUsers(token: string) {
try {
const response = await fetch(
'https://sucu-backend-2024-689509857491.asia-southeast1.run.app/api/v1/users',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
}
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(`Error: ${errorData.message}`);
}

const data = await response.json();
return data;
} catch (error) {
console.error('Failed to fetch users:', error);
}
}
22 changes: 13 additions & 9 deletions src/lib/components/Pagination/Pagination.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { cn } from '../../utils/cn';
import Fa from 'svelte-fa';
import { faChevronLeft, faChevronRight } from '@fortawesome/free-solid-svg-icons';
Expand All @@ -12,9 +13,12 @@
let totalPages: number = Math.ceil(Arrayitem.length / parseInt(itemsPerPage));
let paginatedItems: string[] = [];

const dispatch = createEventDispatcher();

function changePage(page: number | string) {
currentPage = page;
paginateItems();
dispatch('pageChange', { paginatedItems, itemsPerPage });
}

function paginateItems() {
Expand Down Expand Up @@ -45,9 +49,17 @@
return pages;
}

function changeItemsPerPage(newItemsPerPage: string) {
itemsPerPage = newItemsPerPage;
totalPages = Math.ceil(Arrayitem.length / parseInt(itemsPerPage));
paginateItems();
dispatch('pageChange', { paginatedItems, itemsPerPage });
}

$: {
totalPages = Math.ceil(Arrayitem.length / parseInt(itemsPerPage));
paginateItems();
dispatch('pageChange', { paginatedItems, itemsPerPage });
}

$: if (!itemsPerPage || parseInt(itemsPerPage) < 1) {
Expand Down Expand Up @@ -100,18 +112,10 @@
items={pageChoice}
bind:currentChoice={itemsPerPage}
outerClass="w-20 bg-opacity-50 "
on:change={(e) => (itemsPerPage = e.detail)}
on:change={(e) => changeItemsPerPage(e.detail)}
/>
</div>
<div class="flex items-center">/ page</div>
</div>
</div>
</div>

<div class="items-list mt-5">
{#each paginatedItems as item}
<div class="item">
{item}
</div>
{/each}
</div>
69 changes: 63 additions & 6 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,68 @@
<script>
<script lang="ts">
import Footer from '$lib/components/Footer/Footer.svelte';
import Navbar from '$lib/components/Navbar.svelte';
import Sidebar from '$lib/components/Sidebar/Sidebar.svelte';
import { getMe } from '$lib/api/getme';
import { getDecryptedToken } from '$lib/api/getToken';
import '../styles/app.css';
import { onMount } from 'svelte';

let isAdminPage = false;
let isAuthLoginPage = false;
let menuItems = [
{ title: 'ประกาศ', href: '/admin' },
{ title: 'เอกสาร', href: '/admin/documents' },
{ title: 'สถิติ และ งบประมาณ', href: '/admin/stats' },
{ title: 'จัดการผู้ใช้งาน', href: '/admin/users' }
];

let user = {
name: 'Not found',
lastname: 'Not found',
role: 'Not found',
id: 'Not found'
};

onMount(async () => {
const pathname = window.location.pathname;
isAdminPage = pathname.startsWith('/admin');
isAuthLoginPage = pathname === '/auth/login';

if (isAdminPage) {
const token = getDecryptedToken();
if (token) {
try {
const userData = await getMe(token);
user = {
name: userData.result.first_name,
lastname: userData.result.last_name,
role: userData.result.role,
id: userData.result.id
};
} catch (error) {
console.error('Failed to fetch user data:', error);
}
} else {
console.log('No token found');
window.location.replace('/auth/login');
}
}
});
</script>

<div>
<Navbar />
<slot />
<Footer />
</div>
{#if isAdminPage}
<div class="min-h-screen bg-white z-50 w-full">
<Sidebar {menuItems} {user} />
<slot />
</div>
{:else if isAuthLoginPage}
<div>
<slot />
</div>
{:else}
<div>
<Navbar />
<slot />
<Footer />
</div>
{/if}
194 changes: 194 additions & 0 deletions src/routes/admin/users/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<script lang="ts">
import SearchBar from '$lib/components/SearchBar.svelte';
import Button from '$lib/components/Button.svelte';
import Pagination from '$lib/components/Pagination/Pagination.svelte';
import { onMount } from 'svelte';

export let title: string = 'จัดการผู้ใช้งาน';
export let subtitle: string = 'ผู้ใช้งานที่มีสิทธิ์เข้าถึงการแก้ไขและเปลี่ยนแปลงเอกสารทั้งหมด';

let searchQuery = '';
let activePopoverId: string | null = null;

let users = [
{ id: '6753736321', title: 'นางสาวหนูเต็งก เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736322', title: 'นางสาวหนูเต็งก เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736323', title: 'นางสาวหนูเต็งข เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736324', title: 'นางสาวหนูเต็งข เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736325', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736326', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736327', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736328', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736329', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' },
{ id: '6753736320', title: 'นางสาวหนูเต็ง เต็งเต็งเต็ง', date: '01/11/2024', role: 'Admin' }
];

type User = {
id: string;
title: string;
date: string;
role: string;
};

let filteredUsers: User[] = [];
let selectedItems: string[] = [];
let currentPage = 1;
let itemsPerPage = 5;
let paginatedIds: string[] = [];

function togglePopover(id: string) {
activePopoverId = activePopoverId === id ? null : id;
}

function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement;
if (!target.closest('.popover') && !target.closest('.more-options-button')) {
activePopoverId = null;
}
}

function updateFilteredUsers() {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
filteredUsers = users
.filter(
(user) =>
user.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
user.id.includes(searchQuery) ||
user.role.toLowerCase().includes(searchQuery.toLowerCase())
)
.filter((user) => paginatedIds.includes(user.id))
.slice(startIndex, endIndex);
}

$: {
updateFilteredUsers();
}

function toggleSelectAll(event: Event) {
const checkbox = event.target as HTMLInputElement;
if (checkbox.checked) {
selectedItems = filteredUsers.map((user) => user.id);
} else {
selectedItems = [];
}
}

function toggleSelectItem(id: string) {
const index = selectedItems.indexOf(id);
if (index === -1) {
selectedItems = [...selectedItems, id];
} else {
selectedItems = selectedItems.filter((item) => item !== id);
}
}

function handlePageChange(event: CustomEvent) {
paginatedIds = event.detail.paginatedItems;
itemsPerPage = parseInt(event.detail.itemsPerPage);
updateFilteredUsers();
}

onMount(() => {
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
});
</script>

<svelte:window on:click={handleClickOutside} />

<div class="pl-64 min-h-screen bg-white p-8">
<div class="mb-4 mt-5">
<h1 class="text-2xl font-bold text-gray-800">{title}</h1>
<p class="text-sm text-gray-600">{subtitle}</p>
</div>

<div class="mb-4">
<SearchBar bind:value={searchQuery} />
</div>

<div class="rounded-lg">
<!-- Header -->
<div class="flex items-center border-b border-sucu-pink-02 px-4 py-3 mb-2">
<div class="flex w-12 items-center">
<input
type="checkbox"
on:change={toggleSelectAll}
checked={selectedItems.length === filteredUsers.length}
class="h-4 w-4 rounded border-gray-300"
/>
</div>
<div class="flex-1 font-semibold text-sm">เลือกทั้งหมด</div>
<div class="w-32"></div>
<div class="w-40"></div>
<div class="w-12"></div>

<Button
class="w-24 h-8 shadow-none font-normal text-sm"
on:click={() => console.log('Button clicked')}
>
เพิ่มผู้ใช้งาน
</Button>
</div>

<!-- List Items -->
{#each filteredUsers as user}
<div class="flex items-center px-4 py-3 hover:bg-gray-50 shadow-md my-2 relative">
<div class="flex w-12 items-center">
<input
type="checkbox"
checked={selectedItems.includes(user.id)}
on:change={() => toggleSelectItem(user.id)}
class="h-4 w-4 rounded border-gray-300"
/>
</div>
<div class="flex-1 font-medium text-gray-900">{user.title}</div>
<div class="w-32 text-gray-600">{user.id}</div>
<div class="w-40 text-gray-600">สร้างเมื่อ {user.date}</div>
<div class="w-24">
<span class="rounded-full bg-pink-100 px-3 py-1 text-sm font-medium text-pink-800">
{user.role}
</span>
</div>
<div class="w-12 relative">
<button
class="text-gray-500 hover:text-gray-700 more-options-button"
aria-label="More options"
on:click|stopPropagation={() => togglePopover(user.id)}
>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path
d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"
/>
</svg>
</button>
{#if activePopoverId === user.id}
<div class="absolute right-0 mt-2 w-32 rounded-lg bg-white shadow-lg z-50 popover">
<div class="py-1">
<button
class="block w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100"
on:click={() => console.log('Edit user')}
>
แก้ไข
</button>
<button
class="block w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100"
on:click={() => console.log('Remove user')}
>
ลบ
</button>
</div>
</div>
{/if}
</div>
</div>
{/each}

<!-- Pagination -->
<div class="mt-4 w-full flex justify-end">
<Pagination Arrayitem={users.map((user) => user.id)} on:pageChange={handlePageChange} />
</div>
</div>
</div>
Loading