Browse Source

add the products table

gdias 5 months ago
parent
commit
2741559e12

File diff suppressed because it is too large
+ 605 - 513
package-lock.json


+ 3 - 2
package.json

@@ -15,12 +15,13 @@
 		"@sveltejs/adapter-auto": "^6.0.0",
 		"@sveltejs/kit": "^2.22.0",
 		"@sveltejs/vite-plugin-svelte": "^6.0.0",
-		"@tailwindcss/vite": "^4.0.0",
+		"autoprefixer": "^10.4.21",
+		"postcss": "^8.5.6",
 		"prettier": "^3.4.2",
 		"prettier-plugin-svelte": "^3.3.3",
 		"prettier-plugin-tailwindcss": "^0.6.11",
 		"svelte": "^5.0.0",
-		"tailwindcss": "^4.0.0",
+		"tailwindcss": "3.4",
 		"vite": "^7.0.4"
 	}
 }

+ 9 - 0
postcss.config.js

@@ -0,0 +1,9 @@
+import tailwindcss from 'tailwindcss';
+import autoprefixer from 'autoprefixer';
+
+export default {
+	plugins: {
+		tailwindcss: {},
+		autoprefixer: {}
+	}
+};

+ 3 - 1
src/app.css

@@ -1 +1,3 @@
-@import 'tailwindcss';
+@tailwind base;
+@tailwind components;
+@tailwind utilities;

+ 1 - 0
src/lib/assets/add_icon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>

+ 1 - 0
src/lib/assets/save_icon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M840-680v480q0 33-23.5 56.5T760-120H200q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h480l160 160Zm-80 34L646-760H200v560h560v-446ZM480-240q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM240-560h360v-160H240v160Zm-40-86v446-560 114Z"/></svg>

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

@@ -0,0 +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>

+ 1 - 1
src/lib/component/DashBoardGuard.svelte

@@ -7,7 +7,7 @@
 	$: flag = $userFlag;
 
 	onMount(async () => {
-		if (flag === 'b' || flag === '') {
+		if (flag === 'blocked' || flag === '') {
 			goto('/login');
 		} else {
 			autorizado = true;

+ 325 - 0
src/lib/component/Product.svelte

@@ -0,0 +1,325 @@
+<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 products = [];
+	let categories = [];
+
+	let isAddingProduct = false;
+	let editingProductId = null;
+	let productFormData = {
+		name: '',
+		category: '',
+		price: 0,
+		sendToKitchen: false
+	};
+
+	let isAddingCategory = false;
+	let newCategoryName = '';
+	let selectedCategoryFilter = '';
+
+	$: filteredProducts = selectedCategoryFilter
+		? products.filter((p) => p.category === selectedCategoryFilter)
+		: products;
+
+	function resetProductForm() {
+		productFormData = {
+			name: '',
+			category: categories[0]?.name ?? '',
+			price: 0,
+			sendToKitchen: false
+		};
+		editingProductId = null;
+		isAddingProduct = false;
+	}
+
+	function handleProductSubmit(event) {
+		event.preventDefault();
+
+		if (!productFormData.name || !productFormData.category || productFormData.price <= 0) {
+			console.error('Preencha todos os campos corretamente');
+			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!');
+		}
+
+		resetProductForm();
+	}
+
+	function handleEditProduct(product) {
+		productFormData = {
+			name: product.name,
+			category: product.category,
+			price: product.price,
+			sendToKitchen: product.sendToKitchen
+		};
+		editingProductId = product.id;
+		isAddingProduct = true;
+	}
+
+	function handleCategorySubmit(event) {
+		event.preventDefault();
+
+		if (!newCategoryName) {
+			console.error('Digite o nome da categoria');
+			return;
+		}
+		if (categories.some((c) => c.name === newCategoryName)) {
+			console.error('Essa categoria já existe');
+			return;
+		}
+
+		categories = [...categories, { name: newCategoryName }];
+		console.log('Categoria criada com sucesso');
+		newCategoryName = '';
+		isAddingCategory = false;
+	}
+
+	function handleDeleteCategory(name) {
+		categories = categories.filter((c) => c.name !== name);
+		products = products.filter((p) => p.category !== name);
+		if (selectedCategoryFilter === name) selectedCategoryFilter = '';
+	}
+
+	function handleDeleteProduct(id) {
+		products = products.filter((p) => p.id !== id);
+	}
+
+	onMount(() => {
+		const requestOptions = { method: 'GET', redirect: 'follow' };
+
+		fetch('https://fakestoreapi.com/users', requestOptions)
+			.then((response) => response.json())
+			.then((data) => {
+				categories = data.map((categorie) => ({
+					name: categorie.username
+				}));
+			})
+			.catch((error) => console.error(error));
+
+		fetch('https://fakestoreapi.com/users', 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
+				}));
+			})
+			.catch((error) => console.error(error));
+	});
+</script>
+
+<div class="container mx-auto">
+	<div class="flex flex-col">
+		<h1 class="mb-6 text-2xl font-bold">Gerenciar Produtos</h1>
+
+		<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
+			<!-- Categories -->
+			<div class="overflow-hidden rounded-lg bg-gray-800 shadow-lg">
+				<div class="flex items-center justify-between bg-gray-700 p-4">
+					<h2 class="text-lg font-semibold">Categorias</h2>
+					<button
+						on:click={() => (isAddingCategory = 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>
+
+				{#if isAddingCategory}
+					<form on:submit={handleCategorySubmit} class="space-y-4 p-4">
+						<div>
+							<p class="mb-1 block text-sm text-gray-400">Nome da Categoria</p>
+							<input
+								bind:value={newCategoryName}
+								class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+								placeholder="Ex: Bebidas, Porções..."
+							/>
+						</div>
+						<div class="flex space-x-2">
+							<button
+								type="button"
+								on:click={() => {
+									isAddingCategory = false;
+									newCategoryName = '';
+								}}
+								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>
+				{:else}
+					<div class="divide-y divide-gray-700">
+						{#if categories.length === 0}
+							<div class="p-4 text-center text-gray-400">Nenhuma categoria cadastrada</div>
+						{:else}
+							{#each categories as category}
+								<div
+									class="flex cursor-pointer items-center justify-between p-4 hover:bg-gray-900"
+									on:click={() => (selectedCategoryFilter = category.name)}
+								>
+									<span>{category.name}</span>
+									<button
+										on:click|preventDefault|stopPropagation={() =>
+											handleDeleteCategory(category.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" />
+									</button>
+								</div>
+							{/each}
+							{#if selectedCategoryFilter}
+								<div class="p-4">
+									<button
+										on:click={() => (selectedCategoryFilter = '')}
+										class="w-full rounded-md bg-red-600 py-1 text-sm hover:bg-red-700"
+									>
+										Remover Filtro
+									</button>
+								</div>
+							{/if}
+						{/if}
+					</div>
+				{/if}
+			</div>
+
+			<!-- Products -->
+			<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>
+					<button
+						on:click={() => {
+							if (!categories.length) return;
+							resetProductForm(); // já atribui a categoria
+							isAddingProduct = 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>
+
+				{#if isAddingProduct}
+					<form on:submit={handleProductSubmit} class="space-y-4 p-4">
+						<div>
+							<p class="mb-1 block text-sm text-gray-400">Nome do Produto</p>
+							<input
+								bind:value={productFormData.name}
+								class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+								placeholder="Ex: Cerveja..."
+							/>
+						</div>
+
+						<div>
+							<p class="mb-1 block text-sm text-gray-400">Categoria</p>
+							<select
+								bind:value={productFormData.category}
+								class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+							>
+								{#each categories as category}
+									<option value={category.name}>{category.name}</option>
+								{/each}
+							</select>
+						</div>
+
+						<div>
+							<p class="mb-1 block text-sm text-gray-400">Preço (R$)</p>
+							<input
+								type="number"
+								min="0"
+								step="0.01"
+								bind:value={productFormData.price}
+								class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
+								placeholder="0.00"
+							/>
+						</div>
+
+						<div class="flex items-center">
+							<input
+								id="sendToKitchen"
+								type="checkbox"
+								bind:checked={productFormData.sendToKitchen}
+								class="h-4 w-4 rounded border border-gray-600 bg-gray-700"
+							/>
+							<label for="sendToKitchen" class="ml-2 text-sm text-gray-300"
+								>Enviar para a cozinha</label
+							>
+						</div>
+
+						<div class="flex space-x-2">
+							<button
+								type="button"
+								on:click={resetProductForm}
+								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>
+				{:else}
+					<!-- Lista de Produtos -->
+					{#if filteredProducts.length === 0}
+						<p class="p-4 text-center text-gray-400">Nenhum produto encontrado</p>
+					{:else}
+						<ul class="divide-y divide-gray-700">
+							{#each filteredProducts as product}
+								<li
+									class="flex items-center justify-between p-4 hover:bg-gray-900"
+									on:click={() => handleEditProduct(product)}
+								>
+									<div>
+										<p class="text-sm font-semibold text-white">{product.name}</p>
+										<p class="text-xs text-gray-400">
+											{product.category} • R$ {product.price.toFixed(2)}
+										</p>
+									</div>
+									<button
+										on:click|stopPropagation={() => handleDeleteProduct(product.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>
+								</li>
+							{/each}
+						</ul>
+					{/if}
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>

+ 7 - 9
src/lib/layout/SideBar.svelte

@@ -8,7 +8,6 @@
 	import close_icon from '$lib/assets/close_icon.svg';
 	import menu_icon from '$lib/assets/menu_icon.svg';
 	import { userFlag } from '$lib/utils/store';
-	//import { logout } from '../lib/auth'; // Você precisa criar esse arquivo
 	$: flag = $userFlag;
 	$: navItems = [];
 
@@ -31,7 +30,6 @@
 	});
 
 	function handleLogout() {
-		logout();
 		goto('/login');
 	}
 
@@ -58,7 +56,7 @@
 								? 'bg-gray-700 text-white'
 								: 'text-gray-300 hover:bg-gray-700'
 						}`}
-						on:click={() => goto(item.path)}
+						onclick={() => goto(item.path)}
 					>
 						<img
 							src="../src/lib/assets/{item.icon.toLowerCase()}.svg"
@@ -72,8 +70,8 @@
 		</div>
 		<div class="border-t border-gray-700 p-4">
 			<button
-				class="flex w-full items-center justify-center rounded-lg px-4 py-2 text-sm text-red-400 transition-colors hover:bg-gray-700"
-				on:click={handleLogout}
+				class="flex w-full cursor-pointer items-center justify-center rounded-lg px-4 py-2 text-sm text-red-400 transition-colors hover:bg-gray-700"
+				onclick={handleLogout}
 			>
 				<img src={logout_icon} class="mr-2 h-5 w-5" alt="Logout" />
 				<span>Sair</span>
@@ -89,7 +87,7 @@
 			<div class="flex items-center">
 				<button
 					class="mr-4 rounded-md p-1 text-gray-400 hover:text-white"
-					on:click={() => (isMobileMenuOpen = !isMobileMenuOpen)}
+					onclick={() => (isMobileMenuOpen = !isMobileMenuOpen)}
 				>
 					{#if isMobileMenuOpen}
 						<img src={close_icon} class="h-6 w-6" alt="Fechar" />
@@ -115,7 +113,7 @@
 									? 'bg-gray-700 text-white'
 									: 'text-gray-300 hover:bg-gray-700'
 							}`}
-							on:click={() => navigate(item.path)}
+							onclick={() => navigate(item.path)}
 						>
 							<img
 								src="../src/lib/assets/{item.icon.toLowerCase()}.svg"
@@ -126,8 +124,8 @@
 						</button>
 					{/each}
 					<button
-						class="flex items-center rounded-lg px-4 py-3 text-sm text-red-400 transition-colors hover:bg-gray-700"
-						on:click={handleLogout}
+						class="flex cursor-pointer items-center rounded-lg px-4 py-3 text-sm text-red-400 transition-colors hover:bg-gray-700"
+						onclick={handleLogout}
 					>
 						<img src={logout_icon} class="mr-2 h-5 w-5" alt="Logout" />
 						<span>Sair</span>

+ 11 - 0
src/routes/dashboard/products/+page.svelte

@@ -0,0 +1,11 @@
+<script>
+	import Product from '$lib/component/Product.svelte';
+	import SideBar from '$lib/layout/SideBar.svelte';
+	import DashBoardGuard from '$lib/component/DashBoardGuard.svelte';
+</script>
+
+<!--<DashBoardGuard>-->
+<SideBar>
+	<Product />
+</SideBar>
+<!--</DashBoardGuard>-->

+ 25 - 4
src/routes/login/+page.svelte

@@ -1,4 +1,4 @@
-<script lang="ts">
+<script>
 	import { onMount } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { userFlag } from '$lib/utils/store';
@@ -8,6 +8,7 @@
 	let password = '';
 	let loading = false;
 	let error = '';
+	let data;
 
 	let currentUser = false;
 
@@ -29,6 +30,26 @@
 			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');
@@ -88,7 +109,7 @@
 						type="text"
 						bind:value={username}
 						placeholder="Digite seu usuário"
-						class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-3 pr-3 pl-3 text-white focus:border-[#D4AF37] focus:ring-2 focus:ring-[#D4AF37] focus:outline-none"
+						class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-3 pl-3 pr-3 text-white focus:border-[#D4AF37] focus:outline-none focus:ring-2 focus:ring-[#D4AF37]"
 					/>
 				</div>
 			</div>
@@ -104,7 +125,7 @@
 						type="password"
 						bind:value={password}
 						placeholder="••••••••"
-						class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-3 pr-3 pl-3 text-white focus:border-[#D4AF37] focus:ring-2 focus:ring-[#D4AF37] focus:outline-none"
+						class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-3 pl-3 pr-3 text-white focus:border-[#D4AF37] focus:outline-none focus:ring-2 focus:ring-[#D4AF37]"
 					/>
 				</div>
 			</div>
@@ -112,7 +133,7 @@
 			<div>
 				<button
 					type="submit"
-					class="flex w-full cursor-pointer items-center justify-center rounded-md bg-[#D4AF37] px-4 py-3 text-[#1C1C1E] transition-colors hover:bg-[#D4AF37]/90 focus:ring-2 focus:ring-[#D4AF37] focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
+					class="flex w-full cursor-pointer items-center justify-center rounded-md bg-[#D4AF37] px-4 py-3 text-[#1C1C1E] transition-colors hover:bg-[#D4AF37]/90 focus:outline-none focus:ring-2 focus:ring-[#D4AF37] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
 					disabled={loading}
 				>
 					{loading ? 'Entrando...' : 'Entrar'}

+ 9 - 0
tailwind.config.js

@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+import { colors, spacing } from './src/lib/config';
+export default {
+	content: ['./src/**/*.{html,js,svelte,ts}'],
+	theme: {
+		extend: {}
+	},
+	plugins: []
+};

+ 1 - 2
vite.config.js

@@ -1,7 +1,6 @@
-import tailwindcss from '@tailwindcss/vite';
 import { sveltekit } from '@sveltejs/kit/vite';
 import { defineConfig } from 'vite';
 
 export default defineConfig({
-	plugins: [tailwindcss(), sveltekit()]
+	plugins: [sveltekit()]
 });

Some files were not shown because too many files changed in this diff