Ver código fonte

add the addproduct all function (need review) add the report route, dont function

gdias 4 meses atrás
pai
commit
06b121b07f

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

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

+ 1 - 0
src/lib/assets/arrow_back.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="m313-440 224 224-57 56-320-320 320-320 57 56-224 224h487v80H313Z"/></svg>

+ 1 - 0
src/lib/assets/cart_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-80q-33 0-56.5-23.5T200-160q0-33 23.5-56.5T280-240q33 0 56.5 23.5T360-160q0 33-23.5 56.5T280-80Zm400 0q-33 0-56.5-23.5T600-160q0-33 23.5-56.5T680-240q33 0 56.5 23.5T760-160q0 33-23.5 56.5T680-80ZM246-720l96 200h280l110-200H246Zm-38-80h590q23 0 35 20.5t1 41.5L692-482q-11 20-29.5 31T622-440H324l-44 80h480v80H280q-45 0-68-39.5t-2-78.5l54-98-144-304H40v-80h130l38 80Zm134 280h280-280Z"/></svg>

+ 1 - 0
src/lib/assets/remove_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="M200-440v-80h560v80H200Z"/></svg>

+ 1 - 0
src/lib/assets/trash_white.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>

+ 263 - 212
src/lib/component/AddProducts.svelte

@@ -2,90 +2,141 @@
 	import { onMount } from 'svelte';
 	import { goto } from '$app/navigation';
 	import { page } from '$app/stores';
+	import { browser } from '$app/environment';
+
+	import trash_icon from '$lib/assets/trash_white.svg';
+	import arrow_back from '$lib/assets/arrow_back.svg';
+	import add_green from '$lib/assets/add_green.svg';
+	import cart_icon from '$lib/assets/cart_icon.svg';
+
+	let token = null;
+	let company = null;
+	let orderId = null;
+	let tableIdNum = null;
+
+	if (browser) {
+		token = localStorage.getItem('token');
+		company = Number(localStorage.getItem('company'));
+		orderId = Number(localStorage.getItem('order'));
+		tableIdNum = localStorage.getItem('table');
+	}
 
 	let selectedCategory = null;
 	let isPaymentModalOpen = false;
 	let selectedPaymentMethod = null;
 
-	let tableIdNum = 0;
-	let currentTable = undefined;
-	let currentOrder = undefined;
+	let orderItems = [];
 	let products = [];
 	let categories = [];
 
-	$: tableIdNum = parseInt($page.params.tableId || '0');
+	$: totalAmount = orderItems.reduce(
+		(sum, item) => sum + Number(item.product_details?.product_price ?? 0) * (item.quantity ?? 1),
+		0
+	);
 
-	async function fetchTableAndOrder() {
+	async function fetchOrderItems() {
 		try {
-			const resTable = await fetch(`/api/table/${tableIdNum}`);
-			currentTable = await resTable.json();
+			const myHeaders = new Headers();
+			myHeaders.append('Authorization', `Bearer ${token}`);
+			myHeaders.append('Content-Type', 'application/json');
+
+			if (orderId) {
+				const rawItems = JSON.stringify({
+					order_id: orderId,
+					company_id: company
+				});
 
-			if (currentTable?.orderId) {
-				const resOrder = await fetch(`/api/order/${currentTable.orderId}`);
-				currentOrder = await resOrder.json();
+				const resItems = await fetch('https://dev2.mixtech.dev.br/order_item/get', {
+					method: 'POST',
+					headers: myHeaders,
+					body: rawItems
+				});
+				const itemsResult = await resItems.json();
+				orderItems = itemsResult.data || [];
+				orderItems = orderItems.map((item) => ({ ...item, quantity: item.quantity ?? 1 }));
 			} else {
-				currentOrder = undefined;
+				orderItems = [];
 			}
 		} catch (error) {
-			console.error('Erro ao buscar dados da mesa:', error);
+			console.error('Erro ao buscar itens do pedido:', error);
 		}
 	}
 
 	async function fetchProductsAndCategories() {
 		try {
-			const res = await fetch('/api/products');
-			const data = await res.json();
-			products = data.products;
-			categories = data.categories;
+			const myHeaders = new Headers();
+			myHeaders.append('Authorization', `Bearer ${token}`);
+			myHeaders.append('Content-Type', 'application/json');
+
+			const raw = JSON.stringify({
+				company_id: company
+			});
+
+			const requestOptions = {
+				method: 'POST',
+				headers: myHeaders,
+				body: raw,
+				redirect: 'follow'
+			};
+
+			const resCategories = await fetch('https://dev2.mixtech.dev.br/category/get', requestOptions);
+			const categoriesResult = await resCategories.json();
+			categories = categoriesResult.data || [];
+
+			const resProducts = await fetch('https://dev2.mixtech.dev.br/product/get', requestOptions);
+			const productsResult = await resProducts.json();
+			products = productsResult.data || [];
 		} catch (error) {
-			console.error('Erro ao buscar produtos:', error);
+			console.error('Erro ao buscar produtos e categorias:', error);
 		}
 	}
 
 	onMount(async () => {
-		await fetchTableAndOrder();
+		await fetchOrderItems();
 		await fetchProductsAndCategories();
 	});
 
 	$: if (categories.length > 0 && !selectedCategory) {
-		selectedCategory = categories[0].name;
-	}
-
-	$: if (currentTable && currentTable.status === 'FREE') {
-		console.warn('Mesa n\u00e3o est\u00e1 ocupada ou foi liberada.');
-		goto('/tables');
+		selectedCategory = categories[0].category_name;
 	}
 
 	$: filteredProducts = selectedCategory
-		? products.filter((p) => p.category.name === selectedCategory)
+		? products.filter((p) => {
+				const cat = categories.find((c) => c.category_id === p.category_id);
+				return cat?.category_name === selectedCategory;
+			})
 		: products;
 
 	async function handleAddItem(product) {
-		const payload = { productId: product.id, quantity: 1 };
-		await fetch(`/api/order/${currentTable.orderId}/add-item`, {
+		const myHeaders = new Headers();
+		myHeaders.append('Authorization', `Bearer ${token}`);
+		myHeaders.append('Content-Type', 'application/json');
+		const payload = {
+			company_id: company,
+			order_id: orderId,
+			product_id: product.product_id
+		};
+		await fetch('https://dev2.mixtech.dev.br/order_item/create', {
 			method: 'POST',
-			headers: { 'Content-Type': 'application/json' },
+			headers: myHeaders,
 			body: JSON.stringify(payload)
 		});
-		await fetchTableAndOrder();
-	}
-
-	async function updateItemQuantity(tableId, itemId, newQuantity) {
-		await fetch(`/api/order/${currentTable.orderId}/update-item`, {
-			method: 'PUT',
-			headers: { 'Content-Type': 'application/json' },
-			body: JSON.stringify({ itemId, quantity: newQuantity })
-		});
-		await fetchTableAndOrder();
+		await fetchOrderItems();
 	}
 
 	async function removeItemFromOrder(tableId, itemId) {
-		await fetch(`/api/order/${currentTable.orderId}/remove-item`, {
+		const myHeaders = new Headers();
+		myHeaders.append('Authorization', `Bearer ${token}`);
+		myHeaders.append('Content-Type', 'application/json');
+		await fetch('https://dev2.mixtech.dev.br/order_item/delete', {
 			method: 'DELETE',
-			headers: { 'Content-Type': 'application/json' },
-			body: JSON.stringify({ itemId })
+			headers: myHeaders,
+			body: JSON.stringify({
+				company_id: company,
+				item_id: itemId
+			})
 		});
-		await fetchTableAndOrder();
+		await fetchOrderItems();
 	}
 
 	async function handlePaymentConfirm() {
@@ -93,205 +144,205 @@
 			console.warn('Selecione uma forma de pagamento');
 			return;
 		}
-		await fetch(`/api/order/${currentTable.orderId}/close`, {
+		const myHeaders = new Headers();
+		myHeaders.append('Authorization', `Bearer ${token}`);
+		myHeaders.append('Content-Type', 'application/json');
+		await fetch('https://dev2.mixtech.dev.br/order/close', {
 			method: 'POST',
-			headers: { 'Content-Type': 'application/json' },
-			body: JSON.stringify({ paymentMethod: selectedPaymentMethod })
+			headers: myHeaders,
+			body: JSON.stringify({
+				company_id: company,
+				order_id: orderId,
+				payment_method: selectedPaymentMethod
+			})
 		});
 		isPaymentModalOpen = false;
 		goto('/tables');
 	}
 </script>
 
-<!-- restante do componente permanece o mesmo -->
-
-{#if !currentTable || (currentTable.status !== 'FREE' && !currentOrder)}
-	<div class="py-10 text-center">Carregando...</div>
-{:else}
-	<div class="container mx-auto">
-		<div class="mb-6 flex items-center">
-			<button
-				on:click={() => goto('/tables')}
-				class="mr-4 rounded-lg bg-gray-800 p-2 hover:bg-gray-700"
-			>
-				<!-- <img src="/icons/arrow-left.svg" alt="Voltar" class="w-5 h-5" /> -->
-			</button>
-			<h1 class="text-2xl font-bold">Mesa {tableIdNum}</h1>
-		</div>
-
-		<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
-			<div class="overflow-hidden rounded-lg bg-gray-800 shadow-lg lg:col-span-2">
-				<div class="bg-gray-700 p-4">
-					<h2 class="text-lg font-semibold">Adicionar Produtos</h2>
-				</div>
+<div class="container mx-auto">
+	<div class="mb-6 flex items-center">
+		<button
+			on:click={() => goto('/dashboard/commands')}
+			class="mr-4 rounded-lg bg-gray-800 p-2 hover:bg-gray-700"
+		>
+			<img src={arrow_back} alt="Voltar" class="h-5 w-5" />
+		</button>
+		<h1 class="text-2xl font-bold">Mesa {tableIdNum}</h1>
+	</div>
 
-				<div class="bg-gray-750 flex overflow-x-auto border-b border-gray-700 p-2">
-					{#each categories as category}
-						<button
-							class="mx-1 whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium {selectedCategory ===
-							category.name
-								? 'bg-indigo-600 text-white'
-								: 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
-							on:click={() => (selectedCategory = category.name)}
-						>
-							{category.name}
-						</button>
-					{/each}
-				</div>
+	<div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
+		<div class="overflow-hidden rounded-lg bg-gray-800 shadow-lg lg:col-span-2">
+			<div class="bg-gray-700 p-4">
+				<h2 class="text-lg font-semibold">Adicionar Produtos</h2>
+			</div>
 
-				<div
-					class="grid max-h-[calc(100vh-400px)] grid-cols-2 gap-4 overflow-y-auto p-4 sm:grid-cols-3 md:grid-cols-4"
-				>
-					{#each filteredProducts as product}
-						<button
-							class="hover:bg-gray-650 rounded-lg bg-gray-700 p-4 text-left"
-							on:click={() => handleAddItem(product)}
-						>
-							<div class="flex h-full flex-col">
-								<h3 class="mb-2 line-clamp-2 font-medium">{product.name}</h3>
-								<div class="mt-auto flex items-center justify-between">
-									<span class="font-semibold text-emerald-400">R$ {product.price.toFixed(2)}</span>
-									<!-- <img src="/icons/plus.svg" alt="Adicionar" class="w-5 h-5 text-emerald-400" /> -->
-								</div>
-								{#if product.sendToKitchen}
-									<span class="mt-2 rounded-full bg-yellow-800 px-2 py-0.5 text-xs text-yellow-300"
-										>Cozinha</span
-									>
-								{/if}
-							</div>
-						</button>
-					{/each}
-				</div>
+			<div class="bg-gray-750 flex overflow-x-auto border-b border-gray-700 p-2">
+				{#each categories as category}
+					<button
+						class="mx-1 whitespace-nowrap rounded-md px-4 py-2 text-sm font-medium {selectedCategory ===
+						category.category_name
+							? 'bg-indigo-600 text-white'
+							: 'bg-gray-700 text-gray-300 hover:bg-gray-600'}"
+						on:click={() => (selectedCategory = category.category_name)}
+					>
+						{category.category_name}
+					</button>
+				{/each}
 			</div>
 
 			<div
-				class="flex h-full flex-col overflow-hidden rounded-lg bg-gray-800 shadow-lg md:h-[calc(100vh-200px)]"
+				class="grid max-h-[calc(100vh-400px)] grid-cols-2 gap-4 overflow-y-auto p-4 sm:grid-cols-3 md:grid-cols-4"
 			>
-				<div class="bg-gray-700 p-4">
-					<h2 class="text-lg font-semibold">Comanda</h2>
-				</div>
-
-				<div class="flex-1 overflow-y-auto p-4">
-					{#if currentOrder?.items.length === 0}
-						<div class="py-6 text-center text-gray-400">
-							<!-- <img src="/icons/shopping-cart.svg" class="w-10 h-10 mx-auto mb-2 opacity-50" alt="Nenhum item" /> -->
-							<p>Nenhum item adicionado</p>
-						</div>
-					{:else}
-						{#each currentOrder.items as item, index}
-							<div class="bg-gray-750 flex items-center justify-between rounded-lg p-3">
-								<div class="flex-1">
-									<h3 class="font-medium">{item.product.name}</h3>
-									<p class="text-sm text-gray-400">
-										R$ {(item.priceAtOrder ?? 0).toFixed(2)} x {item.quantity}
-									</p>
-								</div>
-								<div class="flex items-center space-x-2">
-									<button
-										on:click={() => updateItemQuantity(tableIdNum, item.id, item.quantity - 1)}
-										class="rounded bg-gray-700 p-1 hover:bg-gray-600"
-									>
-										<!-- <img src="/icons/minus.svg" class="w-4 h-4" alt="Diminuir" /> -->
-									</button>
-									<span class="w-8 text-center">{item.quantity}</span>
-									<button
-										on:click={() => updateItemQuantity(tableIdNum, item.id, item.quantity + 1)}
-										class="rounded bg-gray-700 p-1 hover:bg-gray-600"
-									>
-										<!-- <img src="/icons/plus.svg" class="w-4 h-4" alt="Aumentar" /> -->
-									</button>
-									<button
-										on:click={() => removeItemFromOrder(tableIdNum, item.id)}
-										class="ml-2 rounded bg-red-700 p-1 hover:bg-red-600"
-									>
-										<!-- <img src="/icons/trash.svg" class="w-4 h-4" alt="Remover" /> -->
-									</button>
-								</div>
+				{#each filteredProducts as product}
+					<button
+						class="hover:bg-gray-650 rounded-lg bg-gray-700 p-4 text-left"
+						on:click={() => handleAddItem(product)}
+					>
+						<div class="flex h-full flex-col">
+							<h3 class="mb-2 line-clamp-2 font-medium">
+								{product.product_name ?? 'Nome Indisponível'}
+							</h3>
+							<div class="mt-auto flex items-center justify-between">
+								<span class="font-semibold text-emerald-400"
+									>R$ {Number(product.product_price ?? 0).toFixed(2)}</span
+								>
+								<img src={add_green} alt="Adicionar" class="h-5 w-5 text-emerald-400" />
 							</div>
-						{/each}
-					{/if}
-				</div>
+							{#if product.product_is_kitchen}
+								<span class="mt-2 rounded-full bg-yellow-800 px-2 py-0.5 text-xs text-yellow-300"
+									>Cozinha</span
+								>
+							{/if}
+						</div>
+					</button>
+				{/each}
+			</div>
+		</div>
 
-				<div class="bg-gray-750 border-t border-gray-700 p-4">
-					<div class="mb-2 flex justify-between">
-						<span class="font-medium">Subtotal:</span>
-						<span>R$ {currentOrder?.totalAmount.toFixed(2)}</span>
+		<div
+			class="flex h-full flex-col overflow-hidden rounded-lg bg-gray-800 shadow-lg md:h-[calc(100vh-200px)]"
+		>
+			<div class="bg-gray-700 p-4">
+				<h2 class="text-lg font-semibold">Comanda</h2>
+			</div>
+
+			<div class="flex-1 overflow-y-auto p-4">
+				{#if orderItems.length === 0}
+					<div class="py-6 text-center text-gray-400">
+						<img src={cart_icon} class="mx-auto mb-2 h-10 w-10 opacity-50" alt="Nenhum item" />
+						<p>Nenhum item adicionado</p>
 					</div>
+				{:else}
+					{#each orderItems as item, index}
+						<div class="bg-gray-750 flex items-center justify-between rounded-lg p-3">
+							<div class="flex-1">
+								<h3 class="font-medium">
+									{item.product_details?.product_name ?? 'Nome Indisponível'}
+								</h3>
+								<p class="text-sm text-gray-400">
+									R$ {Number(item.product_details?.product_price ?? 0).toFixed(2)} x {item.quantity ??
+										1}
+								</p>
+							</div>
+							<div class="flex items-center space-x-2">
+								<button
+									on:click={() => removeItemFromOrder(tableIdNum, item.order_item_id)}
+									class="ml-2 rounded bg-red-700 p-1 hover:bg-red-600"
+								>
+									<img src={trash_icon} class="h-4 w-4" alt="Remover" />
+								</button>
+							</div>
+						</div>
+					{/each}
+				{/if}
+			</div>
 
-					<button
-						on:click={() => (isPaymentModalOpen = true)}
-						disabled={currentOrder?.items.length === 0}
-						class="flex w-full items-center justify-center rounded-lg bg-emerald-600 py-3 font-medium hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50"
-					>
-						<!-- <img src="/icons/credit-card.svg" class="w-5 h-5 mr-2" alt="Pagamento" /> -->
-						Fechar Atendimento
-					</button>
+			<div class="bg-gray-750 border-t border-gray-700 p-4">
+				<div class="mb-2 flex justify-between">
+					<span class="font-medium">Subtotal:</span>
+					<span>R$ {totalAmount.toFixed(2)}</span>
 				</div>
+
+				<button
+					on:click={() => (isPaymentModalOpen = true)}
+					disabled={orderItems.length === 0}
+					class="flex w-full items-center justify-center rounded-lg bg-emerald-600 py-3 font-medium hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50"
+				>
+					Fechar Atendimento
+				</button>
 			</div>
 		</div>
+	</div>
 
-		{#if isPaymentModalOpen}
-			<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4">
-				<div class="w-full max-w-lg rounded-lg bg-gray-800 p-6 shadow-xl">
-					<h2 class="mb-4 text-xl font-bold">Finalizar Pedido</h2>
-
-					<div class="mb-6">
-						<h3 class="mb-3 text-lg font-medium">Resumo do Pedido</h3>
-						<div class="mb-4 max-h-60 overflow-y-auto sm:max-h-80">
-							{#each currentOrder.items as item, index}
-								<div class="flex justify-between border-b border-gray-700 py-2">
-									<span>{item.quantity}x {item.product.name}</span>
-									<span>R$ {((item.priceAtOrder ?? 0) * item.quantity).toFixed(2)}</span>
-								</div>
-							{/each}
-						</div>
-						<div class="flex justify-between text-lg font-semibold">
-							<span>Total:</span>
-							<span>R$ {currentOrder.totalAmount.toFixed(2)}</span>
-						</div>
-					</div>
+	{#if isPaymentModalOpen}
+		<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4">
+			<div class="w-full max-w-lg rounded-lg bg-gray-800 p-6 shadow-xl">
+				<h2 class="mb-4 text-xl font-bold">Finalizar Pedido</h2>
 
-					<h3 class="mb-3 text-lg font-medium">Forma de Pagamento</h3>
-					<div class="mb-6 grid grid-cols-1 gap-3 sm:grid-cols-2">
-						{#each ['CASH', 'PIX', 'DEBIT', 'CREDIT'] as method}
-							<button
-								class="flex flex-col items-center justify-center rounded-lg border-2 p-4 transition-colors {selectedPaymentMethod ===
-								method
-									? 'border-emerald-500 bg-emerald-900/20'
-									: 'border-gray-700 hover:border-gray-600'}"
-								on:click={() => (selectedPaymentMethod = method)}
-							>
-								<!-- <img src="/icons/{method.toLowerCase()}.svg" class="w-8 h-8 mb-2" alt={method} /> -->
+				<div class="mb-6">
+					<h3 class="mb-3 text-lg font-medium">Resumo do Pedido</h3>
+					<div class="mb-4 max-h-60 overflow-y-auto sm:max-h-80">
+						{#each orderItems as item}
+							<div class="flex justify-between border-b border-gray-700 py-2">
+								<span
+									>{item.quantity ?? 1}x {item.product_details?.product_name ??
+										'Nome Indisponível'}</span
+								>
 								<span
-									>{method === 'CASH'
-										? 'Dinheiro'
-										: method === 'PIX'
-											? 'Pix'
-											: method === 'DEBIT'
-												? 'Cart\u00e3o de D\u00e9bito'
-												: 'Cart\u00e3o de Cr\u00e9dito'}</span
+									>R$ {(
+										Number(item.product_details?.product_price ?? 0) * (item.quantity ?? 1)
+									).toFixed(2)}</span
 								>
-							</button>
+							</div>
 						{/each}
 					</div>
+					<div class="flex justify-between text-lg font-semibold">
+						<span>Total:</span>
+						<span>R$ {totalAmount.toFixed(2)}</span>
+					</div>
+				</div>
 
-					<div class="flex space-x-3">
+				<h3 class="mb-3 text-lg font-medium">Forma de Pagamento</h3>
+				<div class="mb-6 grid grid-cols-1 gap-3 sm:grid-cols-2">
+					{#each ['CASH', 'PIX', 'DEBIT', 'CREDIT'] as method}
 						<button
-							on:click={() => (isPaymentModalOpen = false)}
-							class="flex-1 rounded-lg bg-gray-700 py-3 font-medium hover:bg-gray-600"
+							class="flex flex-col items-center justify-center rounded-lg border-2 p-4 transition-colors {selectedPaymentMethod ===
+							method
+								? 'border-emerald-500 bg-emerald-900/20'
+								: 'border-gray-700 hover:border-gray-600'}"
+							on:click={() => (selectedPaymentMethod = method)}
 						>
-							Cancelar
-						</button>
-						<button
-							on:click={handlePaymentConfirm}
-							disabled={!selectedPaymentMethod}
-							class="flex-1 rounded-lg bg-emerald-600 py-3 font-medium hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50"
-						>
-							Confirmar Pagamento
+							<span
+								>{method === 'CASH'
+									? 'Dinheiro'
+									: method === 'PIX'
+										? 'Pix'
+										: method === 'DEBIT'
+											? 'Cart\u00e3o de D\u00e9bito'
+											: 'Cart\u00e3o de Cr\u00e9dito'}</span
+							>
 						</button>
-					</div>
+					{/each}
+				</div>
+
+				<div class="flex space-x-3">
+					<button
+						on:click={() => (isPaymentModalOpen = false)}
+						class="flex-1 rounded-lg bg-gray-700 py-3 font-medium hover:bg-gray-600"
+					>
+						Cancelar
+					</button>
+					<button
+						on:click={handlePaymentConfirm}
+						disabled={!selectedPaymentMethod}
+						class="flex-1 rounded-lg bg-emerald-600 py-3 font-medium hover:bg-emerald-700 disabled:cursor-not-allowed disabled:opacity-50"
+					>
+						Confirmar Pagamento
+					</button>
 				</div>
 			</div>
-		{/if}
-	</div>
-{/if}
+		</div>
+	{/if}
+</div>

+ 6 - 1
src/lib/component/Commands.svelte

@@ -103,6 +103,11 @@
 		return `${hours}h ${minutes}m`;
 	};
 
+	function handleAddProduct(order_id) {
+		localStorage.setItem('order', order_id);
+		goto('/dashboard/addproducts');
+	}
+
 	onMount(() => {
 		fetchOrders();
 		setInterval(() => {
@@ -132,7 +137,7 @@
 					<!--adicionar aqui link para o addproduct preciso salvar uma variavel com o order_id e etc para aparecer apenas o item da comanda em especifico-->
 					<button
 						class="mb-2 w-full rounded bg-emerald-600 py-2 hover:bg-emerald-700"
-						on:click={() => goto('/dashboard/addproducts')}
+						on:click={handleAddProduct(order.id)}
 					>
 						Adicionar items
 					</button>

+ 378 - 0
src/lib/component/Report.svelte

@@ -0,0 +1,378 @@
+<script>
+	import { onMount } from 'svelte';
+	// import calendar_icon from '$lib/assets/calendar.svg';
+	// import creditcard_icon from '$lib/assets/creditcard.svg';
+	// import search_icon from '$lib/assets/search.svg';
+	// import filter_icon from '$lib/assets/filter.svg';
+	// import download_icon from '$lib/assets/download.svg';
+	// import dollar_icon from '$lib/assets/dollar.svg';
+	// import trash_icon from '$lib/assets/trash.svg';
+	// import x_icon from '$lib/assets/x.svg';
+	// import arrow_icon from '$lib/assets/arrow.svg';
+
+	let sales = [];
+
+	let dateFilter = '';
+	let paymentMethodFilter = '';
+	let searchTerm = '';
+	let sortBy = 'date';
+	let sortDirection = 'desc';
+	let selectedSale = null;
+	let isDayClosingModalOpen = false;
+
+	onMount(async () => {
+		const res = await fetch('/api/sales'); // trocar as api e rever a logica
+
+		sales = await res.json();
+	});
+
+	function formatDate(dateStr) {
+		const date = new Date(dateStr);
+		return `${date.toLocaleDateString()} ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
+	}
+
+	function getPaymentMethodName(method) {
+		return (
+			{
+				CASH: 'Dinheiro',
+				PIX: 'Pix',
+				DEBIT: 'Cartão de Débito',
+				CREDIT: 'Cartão de Crédito'
+			}[method] || method
+		);
+	}
+
+	$: filteredSales = sales
+		.filter((sale) => {
+			const saleDate = new Date(sale.timestamp).toISOString().slice(0, 10);
+			const includesSearchTerm = sale.items.some((item) =>
+				item.productName.toLowerCase().includes(searchTerm.toLowerCase())
+			);
+			return (
+				(!dateFilter || saleDate === dateFilter) &&
+				(!paymentMethodFilter || sale.paymentMethod === paymentMethodFilter) &&
+				(!searchTerm || includesSearchTerm)
+			);
+		})
+		.sort((a, b) => {
+			if (sortBy === 'date') {
+				return sortDirection === 'asc'
+					? new Date(a.timestamp) - new Date(b.timestamp)
+					: new Date(b.timestamp) - new Date(a.timestamp);
+			} else {
+				return sortDirection === 'asc'
+					? a.totalAmount - b.totalAmount
+					: b.totalAmount - a.totalAmount;
+			}
+		});
+
+	$: salesSummary = {
+		total: filteredSales.reduce((sum, sale) => sum + sale.totalAmount, 0),
+		count: filteredSales.length,
+		byPaymentMethod: {
+			cash: filteredSales
+				.filter((s) => s.paymentMethod === 'CASH')
+				.reduce((sum, s) => sum + s.totalAmount, 0),
+			pix: filteredSales
+				.filter((s) => s.paymentMethod === 'PIX')
+				.reduce((sum, s) => sum + s.totalAmount, 0),
+			debit: filteredSales
+				.filter((s) => s.paymentMethod === 'DEBIT')
+				.reduce((sum, s) => sum + s.totalAmount, 0),
+			credit: filteredSales
+				.filter((s) => s.paymentMethod === 'CREDIT')
+				.reduce((sum, s) => sum + s.totalAmount, 0)
+		},
+		topProducts: Object.entries(
+			filteredSales
+				.flatMap((s) => s.items)
+				.reduce((acc, item) => {
+					if (!acc[item.productName]) acc[item.productName] = { quantity: 0, total: 0 };
+					acc[item.productName].quantity += item.quantity;
+					acc[item.productName].total += (item.priceAtSale || 0) * item.quantity;
+					return acc;
+				}, {})
+		).sort((a, b) => b[1].quantity - a[1].quantity)
+	};
+
+	function toggleSort(field) {
+		if (sortBy === field) {
+			sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
+		} else {
+			sortBy = field;
+			sortDirection = 'desc';
+		}
+	}
+
+	function exportToCSV() {
+		const headers = ['Data', 'Mesa', 'Itens', 'Total', 'Forma de Pagamento'];
+		const rows = filteredSales.map((sale) => [
+			formatDate(sale.timestamp),
+			`Mesa ${sale.tableId}`,
+			sale.items.map((item) => `${item.quantity}x ${item.productName}`).join(', '),
+			`R$ ${sale.totalAmount.toFixed(2)}`,
+			getPaymentMethodName(sale.paymentMethod)
+		]);
+		const csv = [headers, ...rows].map((row) => row.join(',')).join('\n');
+		const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+		const url = URL.createObjectURL(blob);
+		const link = document.createElement('a');
+		link.href = url;
+		link.download = `relatorio_vendas_${new Date().toISOString().slice(0, 10)}.csv`;
+		document.body.appendChild(link);
+		link.click();
+		document.body.removeChild(link);
+	}
+
+	function handleCancelSale(id) {
+		if (confirm('Tem certeza que deseja cancelar esta venda?')) {
+			sales = sales.filter((sale) => sale.id !== id);
+			selectedSale = null;
+		}
+	}
+
+	function handleClosingDay() {
+		console.log('Fechamento do dia concluído.');
+		isDayClosingModalOpen = false;
+	}
+</script>
+
+<div class="flex w-full flex-col">
+	<div class="flex flex-grow flex-col">
+		<!-- Cabeçalho e botões de ação -->
+		<div class="mb-6 flex flex-col justify-between md:flex-row md:items-center">
+			<h1 class="mb-4 text-2xl font-bold md:mb-0">Relatório de Vendas</h1>
+			<div class="flex w-full flex-col gap-2 sm:flex-row sm:justify-end md:w-auto">
+				<button
+					on:click={() => (isDayClosingModalOpen = true)}
+					class="flex w-full items-center justify-center rounded-lg bg-[#D4AF37] px-4 py-2 text-[#1C1C1E] transition-colors hover:bg-[#D4AF37]/90 sm:w-auto"
+				>
+					<!-- <img src={dollar_icon} alt="Fechamento do dia" class="mr-2 h-5 w-5" /> -->
+					Fechamento do Dia
+				</button>
+				<button
+					on:click={exportToCSV}
+					disabled={filteredSales.length === 0}
+					class="flex w-full items-center justify-center rounded-lg bg-[#2C2C2E] px-4 py-2 transition-colors hover:bg-[#3C3C3E] disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
+				>
+					<!-- <img src={download_icon} alt="Exportar CSV" class="mr-2 h-5 w-5" /> -->
+					Exportar CSV
+				</button>
+			</div>
+		</div>
+
+		<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
+			<div class="rounded-lg bg-[#2C2C2E] p-4">
+				<h3 class="mb-2 text-sm text-[#A0A0A0]">Total em Vendas</h3>
+				<p class="text-2xl font-bold text-[#D4AF37]">R$ {salesSummary.total.toFixed(2)}</p>
+				<p class="mt-1 text-sm text-[#A0A0A0]">{salesSummary.count} vendas</p>
+			</div>
+			<div class="rounded-lg bg-[#2C2C2E] p-4">
+				<h3 class="mb-2 text-sm text-[#A0A0A0]">Produtos Mais Vendidos</h3>
+				<div class="space-y-1">
+					{#each salesSummary.topProducts.slice(0, 3) as [name, data]}
+						<div class="flex justify-between text-sm">
+							<span class="text-white">{name}</span>
+							<span class="text-[#A0A0A0]">{data.quantity}x</span>
+						</div>
+					{/each}
+				</div>
+			</div>
+		</div>
+
+		<div class="mb-6 rounded-lg bg-[#2C2C2E] p-4 shadow-lg">
+			<div class="flex flex-col flex-wrap items-start gap-4 md:flex-row md:items-center">
+				<div class="w-full flex-grow md:w-auto">
+					<label for="dateFilter" class="mb-1 block text-sm font-medium text-[#A0A0A0]">Data</label>
+					<div class="relative">
+						<div
+							class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-[#A0A0A0]"
+						>
+							<!-- <img src={calendar_icon} alt="" class="h-5 w-5" /> -->
+						</div>
+						<input
+							type="date"
+							id="dateFilter"
+							bind:value={dateFilter}
+							class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-2 pl-10 pr-3 text-white focus:outline-none focus:ring-2 focus:ring-[#D4AF37]"
+						/>
+					</div>
+				</div>
+
+				<div class="w-full flex-grow md:w-auto">
+					<label for="paymentMethodFilter" class="mb-1 block text-sm font-medium text-[#A0A0A0]"
+						>Forma de Pagamento</label
+					>
+					<div class="relative">
+						<div
+							class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-[#A0A0A0]"
+						>
+							<!-- <img src={creditcard_icon} alt="" class="h-5 w-5" /> -->
+						</div>
+						<select
+							id="paymentMethodFilter"
+							bind:value={paymentMethodFilter}
+							class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-2 pl-10 pr-3 text-white focus:outline-none focus:ring-2 focus:ring-[#D4AF37]"
+						>
+							<option value="">Todas</option>
+							<option value="CASH">Dinheiro</option>
+							<option value="PIX">Pix</option>
+							<option value="DEBIT">Cartão de Débito</option>
+							<option value="CREDIT">Cartão de Crédito</option>
+						</select>
+					</div>
+				</div>
+
+				<div class="w-full flex-grow md:flex-1">
+					<label for="searchTerm" class="mb-1 block text-sm font-medium text-[#A0A0A0]"
+						>Buscar Produtos</label
+					>
+					<div class="relative">
+						<div
+							class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-[#A0A0A0]"
+						>
+							<!-- <img src={search_icon} alt="" class="h-5 w-5" /> -->
+						</div>
+						<input
+							type="text"
+							id="searchTerm"
+							bind:value={searchTerm}
+							placeholder="Busque por nome de produto..."
+							class="block w-full rounded-md border border-[#A0A0A0]/20 bg-[#1C1C1E] py-2 pl-10 pr-3 text-white focus:outline-none focus:ring-2 focus:ring-[#D4AF37]"
+						/>
+					</div>
+				</div>
+
+				<div class="mt-6 w-full self-end md:mt-0 md:w-auto">
+					<button
+						on:click={() => {
+							dateFilter = '';
+							paymentMethodFilter = '';
+							searchTerm = '';
+						}}
+						class="flex w-full items-center justify-center rounded-lg bg-[#1C1C1E] px-4 py-2 transition-colors hover:bg-[#2C2C2E]"
+					>
+						<!-- <img src={filter_icon} alt="" class="mr-2 h-5 w-5" /> -->
+						Limpar Filtros
+					</button>
+				</div>
+			</div>
+		</div>
+
+		<div class="hidden overflow-hidden rounded-lg bg-[#2C2C2E] shadow-lg sm:block">
+			<table class="min-w-full divide-y divide-gray-700">
+				<thead class="sticky top-0 z-10 bg-[#1C1C1E]">
+					<tr>
+						<th
+							class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-[#A0A0A0]"
+						>
+							<button on:click={() => toggleSort('date')} class="flex items-center">
+								Data/Hora
+								<!-- <img
+									src={arrow_icon}
+									class="ml-1 h-4 w-4 {sortBy === 'date' ? 'text-gold' : 'text-gray'}"
+									alt=""
+								/> -->
+							</button>
+						</th>
+						<th>Mesa</th>
+						<th>Itens</th>
+						<th>
+							<button on:click={() => toggleSort('amount')} class="flex items-center">
+								Total
+								<!-- <img
+									src={arrow_icon}
+									class="ml-1 h-4 w-4 {sortBy === 'amount' ? 'text-gold' : 'text-gray'}"
+									alt=""
+								/> -->
+							</button>
+						</th>
+						<th>Pagamento</th>
+						<th class="text-right">Ações</th>
+					</tr>
+				</thead>
+				<tbody class="divide-y divide-gray-700 bg-[#2C2C2E]">
+					{#if filteredSales.length === 0}
+						<tr>
+							<td colspan="6" class="px-6 py-4 text-center text-[#A0A0A0]"
+								>Nenhuma venda encontrada</td
+							>
+						</tr>
+					{:else}
+						{#each filteredSales as sale}
+							<tr class="cursor-pointer hover:bg-[#3C3C3E]" on:click={() => (selectedSale = sale)}>
+								<td class="whitespace-nowrap px-6 py-4 text-sm">{formatDate(sale.timestamp)}</td>
+								<td class="whitespace-nowrap px-6 py-4 text-sm">Mesa {sale.tableId}</td>
+								<td class="px-6 py-4 text-sm">
+									<div class="line-clamp-1">
+										{#each sale.items as item, i}
+											{#if i > 0},
+											{/if}{item.quantity}x {item.productName}
+										{/each}
+									</div>
+								</td>
+								<td class="whitespace-nowrap px-6 py-4 text-sm font-medium"
+									>R$ {sale.totalAmount.toFixed(2)}</td
+								>
+								<td class="whitespace-nowrap px-6 py-4 text-sm"
+									>{getPaymentMethodName(sale.paymentMethod)}</td
+								>
+								<td class="whitespace-nowrap px-6 py-4 text-right">
+									<button
+										on:click|stopPropagation={() => handleCancelSale(sale.id)}
+										class="rounded-lg p-1.5 text-[#FF3B30] hover:bg-[#FF3B30]/20"
+									>
+										<!-- <img src={trash_icon} alt="Cancelar" class="h-4 w-4" /> -->
+									</button>
+								</td>
+							</tr>
+						{/each}
+					{/if}
+				</tbody>
+			</table>
+		</div>
+
+		<div class="rounded-lg bg-[#2C2C2E] shadow-lg sm:hidden">
+			<div class="divide-y divide-gray-700">
+				{#if filteredSales.length === 0}
+					<div class="p-4 text-center text-[#A0A0A0]">Nenhuma venda encontrada</div>
+				{:else}
+					{#each filteredSales as sale}
+						<div
+							class="flex cursor-pointer flex-col space-y-2 p-4 hover:bg-[#3C3C3E]"
+							on:click={() => (selectedSale = sale)}
+						>
+							<div class="flex items-center justify-between">
+								<span class="text-sm font-medium">{formatDate(sale.timestamp)}</span>
+								<span class="text-sm font-medium">Mesa {sale.tableId}</span>
+							</div>
+							<div class="flex items-center justify-between">
+								<span class="text-sm text-[#A0A0A0]">Total:</span>
+								<span class="text-base font-bold text-white">R$ {sale.totalAmount.toFixed(2)}</span>
+							</div>
+							<div class="text-xs text-[#A0A0A0]">
+								Itens: <span class="line-clamp-1 text-white"
+									>{sale.items
+										.map((item) => `${item.quantity}x ${item.productName}`)
+										.join(', ')}</span
+								>
+							</div>
+							<div class="text-xs text-[#A0A0A0]">
+								Pagamento: <span class="text-white">{getPaymentMethodName(sale.paymentMethod)}</span
+								>
+							</div>
+							<div class="mt-2 flex justify-end">
+								<button
+									on:click|stopPropagation={() => handleCancelSale(sale.id)}
+									class="rounded-lg p-1.5 text-[#FF3B30] hover:bg-[#FF3B30]/20"
+								>
+									<!-- <img src={trash_icon} alt="Cancelar" class="h-4 w-4" /> -->
+								</button>
+							</div>
+						</div>
+					{/each}
+				{/if}
+			</div>
+		</div>
+	</div>
+</div>

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

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