Ver Fonte

fix the rotes, add the manager page, add the endpoint on porducts...

gdias há 5 meses atrás
pai
commit
ae0188527e

+ 1 - 1
src/lib/assets/trash_icon.svg

@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#EA3345"><path d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>

+ 5 - 2
src/lib/component/DashBoardGuard.svelte

@@ -1,10 +1,13 @@
 <script>
 	import { onMount } from 'svelte';
 	import { goto } from '$app/navigation';
-	import { userFlag } from '$lib/utils/store';
 	let autorizado = false;
+	import { browser } from '$app/environment';
+	let flag = null;
 
-	$: flag = $userFlag;
+	if (browser) {
+		flag = localStorage.getItem('flag');
+	}
 
 	onMount(async () => {
 		if (flag === 'blocked' || flag === '') {

+ 158 - 0
src/lib/component/Mananger.svelte

@@ -0,0 +1,158 @@
+<script>
+	import { onMount } from 'svelte';
+	import add_icon from '$lib/assets/add_icon.svg';
+	import save_icon from '$lib/assets/save_icon.svg';
+	import trash_icon from '$lib/assets/trash_icon.svg';
+
+	let users = [];
+	let isAddingUser = false;
+	let newUserForm = {
+		name: '',
+		username: '',
+		email: ''
+	};
+
+	function resetUserForm() {
+		newUserForm = {
+			name: '',
+			username: '',
+			email: ''
+		};
+		isAddingUser = false;
+	}
+
+	function handleAddUser(event) {
+		event.preventDefault();
+
+		if (!newUserForm.name || !newUserForm.username || !newUserForm.email) {
+			console.error('Preencha todos os campos corretamente');
+			return;
+		}
+
+		const newUser = {
+			id: Date.now(),
+			...newUserForm
+		};
+		users = [...users, newUser];
+		console.log('Usuário criado com sucesso!');
+
+		resetUserForm();
+	}
+
+	function handleDeleteUser(id) {
+		users = users.filter((u) => u.id !== id);
+	}
+
+	onMount(() => {
+		fetch('https://fakestoreapi.com/users')
+			.then((response) => response.json())
+			.then((data) => {
+				users = data.map((item) => ({
+					id: item.id,
+					name: `${item.name.firstname} ${item.name.lastname}`,
+					username: item.username,
+					email: item.email
+				}));
+			})
+			.catch((error) => console.error(error));
+	});
+</script>
+
+<div class="container mx-auto">
+	<div class="flex flex-col">
+		<div class="mb-6 flex items-center justify-between">
+			<h1 class="text-2xl font-bold">Gerenciar Usuários</h1>
+			<button
+				on:click={() => (isAddingUser = true)}
+				class="rounded-lg bg-emerald-600 p-1.5 hover:bg-emerald-700"
+			>
+				<img src={add_icon} alt="Adicionar" class="h-4 w-4" />
+			</button>
+		</div>
+
+		<div class="overflow-hidden rounded-lg bg-gray-800 shadow-lg">
+			<table class="w-full divide-y divide-gray-700">
+				<thead class="bg-gray-700">
+					<tr>
+						<th class="px-6 py-3 text-left text-sm font-semibold text-gray-300">Nome</th>
+						<th class="px-6 py-3 text-left text-sm font-semibold text-gray-300">Username</th>
+						<th class="px-6 py-3 text-left text-sm font-semibold text-gray-300">Email</th>
+						<th class="px-6 py-3 text-left text-sm font-semibold text-gray-300">Ações</th>
+					</tr>
+				</thead>
+				<tbody class="divide-y divide-gray-700">
+					{#if users.length === 0}
+						<tr>
+							<td colspan="4" class="p-4 text-center text-gray-400">Nenhum usuário encontrado</td>
+						</tr>
+					{:else}
+						{#each users as user}
+							<tr class="hover:bg-gray-900">
+								<td class="px-6 py-4 text-sm text-white">{user.name}</td>
+								<td class="px-6 py-4 text-sm text-white">{user.username}</td>
+								<td class="px-6 py-4 text-sm text-white">{user.email}</td>
+								<td class="px-6 py-4">
+									<button
+										on:click={() => handleDeleteUser(user.id)}
+										class="rounded-lg p-1.5 text-red-400 hover:bg-red-900/20"
+									>
+										<img src={trash_icon} alt="Deletar" class="h-4 w-4" />
+									</button>
+								</td>
+							</tr>
+						{/each}
+					{/if}
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+{#if isAddingUser}
+	<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
+		<div class="w-full max-w-md rounded-lg bg-gray-800 p-6 shadow-lg">
+			<h2 class="mb-4 text-lg font-semibold">Adicionar Novo Usuário</h2>
+			<form on:submit={handleAddUser} class="space-y-4">
+				<div>
+					<p class="mb-1 block text-sm text-gray-400">Nome</p>
+					<input
+						bind:value={newUserForm.name}
+						class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+						placeholder="Ex: John Doe"
+					/>
+				</div>
+				<div>
+					<p class="mb-1 block text-sm text-gray-400">Username</p>
+					<input
+						bind:value={newUserForm.username}
+						class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+						placeholder="Ex: johndoe"
+					/>
+				</div>
+				<div>
+					<p class="mb-1 block text-sm text-gray-400">Email</p>
+					<input
+						bind:value={newUserForm.email}
+						class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+						placeholder="Ex: john@example.com"
+					/>
+				</div>
+				<div class="flex space-x-2">
+					<button
+						type="button"
+						on:click={resetUserForm}
+						class="rounded-lg bg-gray-700 px-4 py-2 hover:bg-gray-600"
+					>
+						Cancelar
+					</button>
+					<button
+						type="submit"
+						class="flex items-center rounded-lg bg-emerald-600 px-4 py-2 hover:bg-emerald-700"
+					>
+						<img src={save_icon} alt="Salvar" class="mr-2 h-4 w-4" /> Salvar
+					</button>
+				</div>
+			</form>
+		</div>
+	</div>
+{/if}

+ 177 - 48
src/lib/component/Product.svelte

@@ -7,6 +7,7 @@
 	let products = [];
 	let categories = [];
 
+	let noCategory = false;
 	let isAddingProduct = false;
 	let editingProductId = null;
 	let productFormData = {
@@ -15,6 +16,7 @@
 		price: 0,
 		sendToKitchen: false
 	};
+	let associatedProduct = false;
 
 	let isAddingCategory = false;
 	let newCategoryName = '';
@@ -43,26 +45,45 @@
 			return;
 		}
 
-		if (editingProductId) {
-			const index = products.findIndex((p) => p.id === editingProductId);
-			if (index !== -1) {
-				products[index] = {
-					...products[index],
-					...productFormData
-				};
-				products = products; // Força reatividade
-				console.log('Produto atualizado com sucesso!');
-			}
-		} else {
-			const newProduct = {
-				id: Date.now(),
-				...productFormData
-			};
-			products = [...products, newProduct];
-			console.log('Produto criado com sucesso!');
+		const categoryObj = categories.find((c) => c.name === productFormData.category);
+		if (!categoryObj) {
+			console.error('Categoria inválida');
+			return;
 		}
 
-		resetProductForm();
+		const payload = {
+			product_name: productFormData.name,
+			product_price: Number(productFormData.price),
+			category_id: categoryObj.id,
+			company_id: 1
+		};
+
+		const requestOptions = {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(payload),
+			redirect: 'follow'
+		};
+
+		fetch('https://dev2.mixtech.dev.br/product/create', requestOptions)
+			.then((response) => response.json())
+			.then((res) => {
+				if (res.status === 'ok') {
+					const newProduct = {
+						id: res.data?.product_id || Date.now(),
+						...productFormData
+					};
+					products = [...products, newProduct];
+					console.log('Produto criado com sucesso!');
+					resetProductForm();
+					location.reload();
+				} else {
+					console.error('Erro ao criar produto:', res.msg);
+				}
+			})
+			.catch((error) => {
+				console.error('Erro na requisição:', error);
+			});
 	}
 
 	function handleEditProduct(product) {
@@ -88,46 +109,137 @@
 			return;
 		}
 
-		categories = [...categories, { name: newCategoryName }];
-		console.log('Categoria criada com sucesso');
-		newCategoryName = '';
-		isAddingCategory = false;
+		const requestOptions = {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify({
+				category_name: newCategoryName,
+				category_is_kitchen: false,
+				company_id: 1
+			}),
+			redirect: 'follow'
+		};
+
+		fetch('https://dev2.mixtech.dev.br/category/create', requestOptions)
+			.then((response) => response.json())
+			.then((res) => {
+				if (res.status === 'ok') {
+					categories = [...categories, { name: newCategoryName }];
+					console.log('Categoria criada com sucesso');
+					newCategoryName = '';
+					isAddingCategory = false;
+					location.reload();
+				} else {
+					console.error('Erro ao criar categoria:', res.msg);
+				}
+			})
+			.catch((error) => {
+				console.error('Erro na requisição:', error);
+			});
 	}
 
 	function handleDeleteCategory(name) {
-		categories = categories.filter((c) => c.name !== name);
-		products = products.filter((p) => p.category !== name);
-		if (selectedCategoryFilter === name) selectedCategoryFilter = '';
+		const hasAssociatedProducts = products.some((p) => p.category === name);
+
+		if (hasAssociatedProducts) {
+			associatedProduct = true;
+			return;
+		}
+
+		fetch('https://dev2.mixtech.dev.br/category/delete', {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify({
+				category_name: name,
+				company_id: 1
+			}),
+			redirect: 'follow'
+		})
+			.then((response) => response.json())
+			.then((res) => {
+				if (res.status === 'ok') {
+					categories = categories.filter((c) => c.name !== name);
+					if (selectedCategoryFilter === name) selectedCategoryFilter = '';
+					console.log('Categoria deletada com sucesso!');
+				} else {
+					console.error('Erro ao deletar categoria:', res.msg);
+				}
+			})
+			.catch((error) => {
+				console.error('Erro na requisição:', error);
+			});
 	}
 
-	function handleDeleteProduct(id) {
-		products = products.filter((p) => p.id !== id);
+	function handleDeleteProduct(productName) {
+		const requestOptions = {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify({
+				product_name: productName,
+				company_id: 1
+			}),
+			redirect: 'follow'
+		};
+
+		fetch('https://dev2.mixtech.dev.br/product/delete', requestOptions)
+			.then((response) => response.json())
+			.then((res) => {
+				if (res.status === 'ok') {
+					products = products.filter((p) => p.name !== productName);
+					console.log('Produto deletado com sucesso!');
+				} else {
+					console.error('Erro ao deletar produto:', res.msg);
+				}
+			})
+			.catch((error) => {
+				console.error('Erro na requisição:', error);
+			});
 	}
 
 	onMount(() => {
-		const requestOptions = { method: 'GET', redirect: 'follow' };
+		const companyId = 1;
+
+		const requestOptions = {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			redirect: 'follow',
+			body: JSON.stringify({ company_id: companyId })
+		};
 
-		fetch('https://fakestoreapi.com/users', requestOptions)
+		fetch('https://dev2.mixtech.dev.br/category/get', requestOptions)
 			.then((response) => response.json())
-			.then((data) => {
-				categories = data.map((categorie) => ({
-					name: categorie.username
-				}));
+			.then((res) => {
+				if (res.status === 'ok') {
+					categories = res.data.map((categorie) => ({
+						id: categorie.category_id,
+						name: categorie.category_name,
+						isKitchen: categorie.category_is_kitchen === 1
+					}));
+				} else {
+					console.error('Erro ao carregar categorias:', res.msg);
+				}
 			})
-			.catch((error) => console.error(error));
+			.catch((error) => console.error('Erro ao buscar categorias:', error));
 
-		fetch('https://fakestoreapi.com/users', requestOptions)
+		fetch('https://dev2.mixtech.dev.br/product/get', requestOptions)
 			.then((response) => response.json())
-			.then((data) => {
-				products = data.map((item, index) => ({
-					id: Date.now() + index,
-					name: item.address.street,
-					category: item.username,
-					price: Math.floor(Math.random() * 50 + 10),
-					sendToKitchen: Math.random() > 0.5
-				}));
+			.then((res) => {
+				if (res.status === 'ok') {
+					products = res.data.map((item) => {
+						const category = categories.find((c) => c.id === item.category_id);
+						return {
+							id: item.product_id,
+							name: item.product_name,
+							category: category?.name || 'Sem categoria',
+							price: Number(item.product_price),
+							sendToKitchen: category?.isKitchen || false
+						};
+					});
+				} else {
+					console.error('Erro ao carregar produtos:', res.msg);
+				}
 			})
-			.catch((error) => console.error(error));
+			.catch((error) => console.error('Erro ao buscar produtos:', error));
 	});
 </script>
 
@@ -207,6 +319,13 @@
 									</button>
 								</div>
 							{/if}
+							{#if associatedProduct}
+								<div class="p-4">
+									<p class="w-full rounded-md bg-yellow-600 p-1 text-sm">
+										Delete os produtos associados a esta categoria
+									</p>
+								</div>
+							{/if}
 						{/if}
 					</div>
 				{/if}
@@ -216,11 +335,21 @@
 			<div class="overflow-hidden rounded-lg bg-gray-800 shadow-lg lg:col-span-2">
 				<div class="flex items-center justify-between bg-gray-700 p-4">
 					<h2 class="text-lg font-semibold">Produtos</h2>
+					{#if noCategory}
+						<div>
+							<p class="w-full rounded-md bg-yellow-600 p-1 text-sm">Primeiro crie uma categoria</p>
+						</div>
+					{/if}
 					<button
 						on:click={() => {
-							if (!categories.length) return;
-							resetProductForm(); // já atribui a categoria
-							isAddingProduct = true;
+							if (!categories.length) {
+								noCategory = true;
+								return;
+							} else {
+								noCategory = false;
+								resetProductForm();
+								isAddingProduct = true;
+							}
 						}}
 						class="rounded-lg bg-emerald-600 p-1.5 hover:bg-emerald-700"
 					>
@@ -309,7 +438,7 @@
 										</p>
 									</div>
 									<button
-										on:click|stopPropagation={() => handleDeleteProduct(product.id)}
+										on:click|stopPropagation={() => handleDeleteProduct(product.name)}
 										class="rounded-lg p-1.5 text-red-400 hover:bg-red-900/20"
 									>
 										<img src={trash_icon} alt="Deletar" class="h-4 w-4" />

+ 8 - 3
src/lib/layout/SideBar.svelte

@@ -7,8 +7,12 @@
 	import logout_icon from '$lib/assets/logout_icon.svg';
 	import close_icon from '$lib/assets/close_icon.svg';
 	import menu_icon from '$lib/assets/menu_icon.svg';
-	import { userFlag } from '$lib/utils/store';
-	$: flag = $userFlag;
+	import { browser } from '$app/environment';
+	let flag = null;
+
+	if (browser) {
+		flag = localStorage.getItem('flag');
+	}
 	$: navItems = [];
 
 	let isMobileMenuOpen = false;
@@ -22,7 +26,7 @@
 				{ name: 'Produtos', path: '/dashboard/products', icon: 'product_sell' },
 				{ name: 'Relatórios', path: '/dashboard/reports', icon: 'report_icon' },
 				{ name: 'Cozinha', path: '/dashboard/cozinha', icon: 'kitchen_icon' },
-				{ name: 'Gerenciar Usuários', path: '/dashboard/control', icon: 'mananger' }
+				{ name: 'Gerenciar Usuários', path: '/dashboard/mananger', icon: 'mananger' }
 			];
 		} else if (flag == 'kitchen') {
 			navItems = [{ name: 'Cozinha', path: '/dashboard/cozinha', icon: 'kitchen_icon' }];
@@ -30,6 +34,7 @@
 	});
 
 	function handleLogout() {
+		localStorage.removeItem('flag');
 		goto('/login');
 	}
 

+ 58 - 0
src/lib/utils/token.js

@@ -0,0 +1,58 @@
+import { tokenValidation } from '../store';
+
+export function setTokenCookie(token) {
+	document.cookie = `token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
+
+	const expiration = new Date(Date.now() + 30 * 60 * 1000);
+	document.cookie = `token=${token}-time=${expiration.toUTCString()}; path=/; expires=${expiration.toUTCString()}`;
+}
+
+export function getToken() {
+	const tokenCookie = document.cookie.split('; ').find((row) => row.startsWith('token='));
+
+	if (tokenCookie == null) return null;
+
+	const [tokenPart] = tokenCookie.split('-time=');
+	return tokenPart.replace('token=', '');
+}
+
+export function checkTokenExpiry() {
+	const tokenCookie = document.cookie.split('; ').find((row) => row.startsWith('token='));
+	if (tokenCookie == null) {
+		tokenValidation.set(true);
+		return;
+	}
+
+	const [tokenPart, timePart] = tokenCookie.split('-time=');
+
+	if (timePart == null) {
+		tokenValidation.set(true);
+		return;
+	}
+
+	const expirationTime = new Date(timePart);
+	const now = new Date();
+	const timeDiff = expirationTime - now;
+
+	if (timeDiff <= 0) {
+		tokenValidation.set(true);
+	} else if (timeDiff <= 5 * 60 * 1000) {
+		const myHeaders = new Headers();
+		myHeaders.append('Content-Type', 'application/json');
+
+		const raw = JSON.stringify({
+			email: localStorage.getItem('email')
+		});
+		const requestOptions = {
+			method: 'POST',
+			headers: myHeaders,
+			body: raw,
+			redirect: 'follow'
+		};
+
+		fetch('http://localhost:3005/api/refresh-token', requestOptions)
+			.then((response) => response.json())
+			.then((result) => setTokenCookie(result.data.token));
+	}
+	tokenValidation.set(false);
+}

+ 8 - 0
src/routes/dashboard/mananger/+page.svelte

@@ -0,0 +1,8 @@
+<script>
+	import SideBar from '$lib/layout/SideBar.svelte';
+	import Mananger from '$lib/component/Mananger.svelte';
+</script>
+
+<SideBar>
+	<Mananger />
+</SideBar>

+ 43 - 51
src/routes/login/+page.svelte

@@ -9,15 +9,14 @@
 	let loading = false;
 	let error = '';
 	let data;
-
 	let currentUser = false;
 
 	onMount(() => {
 		if (currentUser) {
-			//need check this and tested
 			goto('/tables');
 		}
 	});
+
 	function passwordForgotten() {
 		goto('/forgotten');
 	}
@@ -30,56 +29,49 @@
 			return;
 		}
 
-		const myHeaders = new Headers();
-		myHeaders.append('Content-Type', 'application/json');
-
-		const raw = JSON.stringify({
-			username: username,
-			password: password
-		});
-
-		const requestOptions = {
-			method: 'POST',
-			headers: myHeaders,
-			body: raw,
-			redirect: 'follow'
-		};
-
-		fetch('http://dev2.mixtech.dev.br/login', requestOptions)
-			.then((response) => response.text())
-			.then((result) => console.log(result))
-			.catch((error) => console.error(error));
-
-		if (username == 'admin' && password == 'admin') {
-			userFlag.set('admin');
-			goto('/dashboard/tables');
-		} else if (username == 'waiter' && password == 'waiter') {
-			userFlag.set('waiter');
-			goto('/dashboard/tables');
-		} else if (username == 'kitchen' && password == 'kitchen') {
-			userFlag.set('kitchen');
-			goto('/dashboard/tables');
-		} else {
-			error = 'Usuário ou senha inválidos';
-			return;
+		error = '';
+		loading = true;
+
+		try {
+			const response = await fetch('https://dev2.mixtech.dev.br/login', {
+				method: 'POST',
+				headers: {
+					'Content-Type': 'application/json'
+				},
+				body: JSON.stringify({
+					username,
+					password
+				})
+			});
+
+			const result = await response.json();
+			data = result;
+
+			if (data.status === 'ok') {
+				const id = data.data.role_id;
+				switch (id) {
+					case 1:
+						localStorage.setItem('flag', 'admin');
+						break;
+					case 2:
+						localStorage.setItem('flag', 'waiter');
+						break;
+					case 3:
+						localStorage.setItem('flag', 'kitchen');
+						break;
+					default:
+						localStorage.setItem('flag', '');
+				}
+				goto('/dashboard/tables');
+			} else {
+				error = 'Usuário ou senha inválidos';
+			}
+		} catch (err) {
+			console.error(err);
+			error = 'Erro ao conectar com o servidor';
+		} finally {
+			loading = false;
 		}
-
-		// try {
-		//   loading = true;
-		//   error = '';
-		//   const success = await login(username, password);
-
-		//   if (success) {
-		//     goto('/tables');
-		//   } else {
-		//     error = 'Usuário ou senha inválidos';
-		//   }
-		// } catch (err) {
-		//   console.error(err);
-		//   error = 'Falha ao fazer login';
-		// } finally {
-		//   loading = false;
-		// }
 	}
 </script>