| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- <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';
- import edit_icon from '$lib/assets/edit_icon.svg';
- import { browser } from '$app/environment';
- import check_icon from '$lib/assets/check_icon.svg';
- import cancel_icon from '$lib/assets/cancel_icon.svg';
- let token = null;
- let company = null;
- if (browser) {
- token = localStorage.getItem('token');
- company = Number(localStorage.getItem('company'));
- localStorage.setItem('typePage', 'false');
- }
- let products = [];
- let categories = [];
- let noCategory = false;
- let isAddingProduct = false;
- let editingProductId = null;
- let isEditingProduct = false;
- let productFormData = {
- name: '',
- category: '',
- price: 0,
- sendToKitchen: false
- };
- let associatedProduct = false;
- let isAddingCategory = false;
- let newCategoryName = '';
- let selectedCategoryFilter = '';
- $: filteredProducts = selectedCategoryFilter
- ? products.filter((p) => p.category === selectedCategoryFilter)
- : products;
- function resetProductForm() {
- productFormData = {
- name: '',
- description: '',
- category: categories[0]?.name ?? '',
- price: 0,
- sendToKitchen: false
- };
- editingProductId = null;
- isAddingProduct = false;
- isEditingProduct = false;
- }
- function handleProductSubmit(event) {
- event.preventDefault();
- if (!productFormData.name || !productFormData.category || productFormData.price <= 0) {
- console.error('Preencha todos os campos corretamente');
- return;
- }
- const categoryObj = categories.find((c) => c.name === productFormData.category);
- if (!categoryObj) {
- console.error('Categoria inválida');
- return;
- }
- const payload = {
- product_name: productFormData.name,
- product_price: Number(productFormData.price),
- category_id: categoryObj.id,
- company_id: company,
- product_is_kitchen: productFormData.sendToKitchen
- };
- const requestOptions = {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
- },
- body: JSON.stringify(payload),
- redirect: 'follow'
- };
- fetch('https://bartender.mixtab.com.br/product/create', requestOptions)
- .then((response) => response.json())
- .then((res) => {
- if (res.status === 'ok') {
- const productId = res.data?.product_id;
- if (!productId) {
- console.error('Produto criado, mas ID não retornado.');
- return;
- }
- // Chamada para adicionar a descrição
- const descPayload = {
- product_id: productId,
- company_id: company,
- description_text: productFormData.description
- };
- fetch('https://bartender.mixtab.com.br/description/create', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
- },
- body: JSON.stringify(descPayload),
- redirect: 'follow'
- })
- .then((descRes) => descRes.json())
- .then((descResult) => {
- if (descResult.status === 'ok') {
- console.log('Descrição adicionada com sucesso!');
- } else {
- console.error('Erro ao adicionar descrição:', descResult.msg);
- }
- })
- .catch((error) => console.error('Erro na requisição de descrição:', error));
- const newProduct = {
- id: productId,
- ...productFormData
- };
- products = [...products, newProduct];
- //console.log('Produto criado com sucesso!');
- resetProductForm();
- fetchAllItems();
- } else {
- console.error('Erro ao criar produto:', res.msg);
- }
- })
- .catch((error) => {
- console.error('Erro na requisição:', error);
- });
- }
- function handleEditProduct(product) {
- productFormData = {
- name: product.name,
- category: product.category,
- price: product.price,
- sendToKitchen: product.sendToKitchen
- };
- editingProductId = product.id;
- isEditingProduct = true;
- }
- async function handleUpdateProduct(event) {
- event.preventDefault();
- token = localStorage.getItem('token');
- company = Number(localStorage.getItem('company'));
- const myHeaders = new Headers();
- myHeaders.append('Authorization', `Bearer ${token}`);
- myHeaders.append('Content-Type', 'application/json');
- const raw = JSON.stringify({
- update_product_id: editingProductId,
- product_name: productFormData.name,
- product_price: Number(productFormData.price),
- product_is_kitchen: productFormData.sendToKitchen,
- company_id: company
- });
- const requestOptions = {
- method: 'POST',
- headers: myHeaders,
- body: raw,
- redirect: 'follow'
- };
- try {
- const response = await fetch(
- 'https://bartender.mixtab.com.br/product/update',
- requestOptions
- );
- const result = await response.json();
- if (result.status === 'ok' && productFormData.description != '') {
- // Atualizar a descrição após o produto
- const descPayload = {
- product_id: editingProductId,
- company_id: company,
- description_text: productFormData.description
- };
- try {
- const descRes = await fetch('https://bartender.mixtab.com.br/description/update', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
- },
- body: JSON.stringify(descPayload),
- redirect: 'follow'
- });
- const descResult = await descRes.json();
- if (descResult.status === 'ok') {
- console.log('Descrição atualizada com sucesso!');
- } else {
- console.error('Erro ao atualizar descrição:', descResult.msg);
- }
- } catch (error) {
- console.error(error);
- }
- isEditingProduct = false;
- editingProductId = null;
- fetchAllItems(); // para recarregar lista
- } else {
- console.error('Erro ao atualizar produto:', result.msg || result);
- }
- isEditingProduct = false;
- } catch (error) {
- console.error('Erro na requisição de atualização:', error);
- }
- }
- 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;
- }
- const requestOptions = {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
- body: JSON.stringify({
- category_name: newCategoryName,
- category_is_kitchen: false,
- company_id: company
- }),
- redirect: 'follow'
- };
- fetch('https://bartender.mixtab.com.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;
- fetchAllItems();
- } else {
- console.error('Erro ao criar categoria:', res.msg);
- }
- })
- .catch((error) => {
- console.error('Erro na requisição:', error);
- });
- }
- function handleDeleteCategory(name) {
- const hasAssociatedProducts = products.some((p) => p.category === name);
- if (hasAssociatedProducts) {
- associatedProduct = true;
- return;
- }
- fetch('https://bartender.mixtab.com.br/category/delete', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
- body: JSON.stringify({
- category_name: name,
- company_id: company
- }),
- 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(productName) {
- const requestOptions = {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
- body: JSON.stringify({
- product_name: productName,
- company_id: company
- }),
- redirect: 'follow'
- };
- fetch('https://bartender.mixtab.com.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);
- });
- }
- function fetchAllItems() {
- const requestOptions = {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${token}`
- },
- redirect: 'follow',
- body: JSON.stringify({ company_id: company })
- };
- fetch('https://bartender.mixtab.com.br/category/get', requestOptions)
- .then((response) => response.json())
- .then((res) => {
- if (res.status === 'ok') {
- categories = res.data.map((categorie) => ({
- id: categorie.category_id,
- name: categorie.category_name
- }));
- return fetch('https://bartender.mixtab.com.br/product/get', requestOptions);
- } else {
- throw new Error(`Erro ao carregar categorias: ${res.msg}`);
- }
- })
- .then((response) => response.json())
- .then((res) => {
- if (res.status === 'ok') {
- //console.log(res);
- 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: item.product_is_kitchen
- };
- });
- } else {
- console.error('Erro ao carregar produtos:', res.msg);
- }
- })
- .catch((error) => {
- console.error('Erro ao buscar dados:', error);
- });
- }
- onMount(() => {
- fetchAllItems();
- });
- </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 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}
- </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>
- {#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) {
- noCategory = true;
- return;
- } else {
- noCategory = false;
- resetProductForm();
- 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">Descrição do Produto</p>
- <input
- bind:value={productFormData.description}
- class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
- placeholder="Descrição"
- />
- </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 if isEditingProduct}
- <form on:submit={handleUpdateProduct} 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">Descrição do Produto</p>
- <input
- bind:value={productFormData.description}
- class="w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 focus:ring-emerald-500"
- placeholder="Descrição"
- />
- </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}
- <div class="overflow-x-auto rounded-lg">
- <table
- class="min-w-full table-auto divide-y divide-gray-700 text-left text-sm text-white"
- >
- <thead class="bg-gray-800">
- <tr>
- <th class="px-6 py-3 font-semibold">Nome</th>
- <th class="px-6 py-3 font-semibold">Categoria</th>
- <th class="px-6 py-3 font-semibold">Preço</th>
- <th class="px-6 py-3 font-semibold">Cozinha</th>
- <th class="px-6 py-3 text-center font-semibold">Ações</th>
- </tr>
- </thead>
- <tbody class="divide-y divide-gray-700 bg-gray-800">
- {#each filteredProducts as product}
- <tr class="transition-colors hover:bg-gray-800">
- <td class="px-6 py-4">{product.name}</td>
- <td class="px-6 py-4 text-gray-400">{product.category}</td>
- <td class="px-6 py-4 text-gray-400">R$ {product.price.toFixed(2)}</td>
- <td class="px-6 py-4">
- <div class="mr-1 flex items-center justify-center">
- {#if product.sendToKitchen}
- <img class="h-4 w-4" src={check_icon} alt="check" />
- {:else}
- <img class="h-5 w-5" src={cancel_icon} alt="cancel" />
- {/if}
- </div>
- </td>
- <td class="px-6 py-4">
- <div class="flex justify-center gap-4">
- <button
- on:click={() => handleEditProduct(product)}
- class="rounded-lg text-blue-400 hover:bg-blue-900/20"
- >
- <img src={edit_icon} alt="Editar" class="h-4 w-4" />
- </button>
- <button
- on:click|stopPropagation={() => handleDeleteProduct(product.name)}
- class="rounded-lg text-red-400 hover:bg-red-900/20"
- >
- <img src={trash_icon} alt="Deletar" class="h-4 w-4" />
- </button>
- </div>
- </td>
- </tr>
- {/each}
- </tbody>
- </table>
- </div>
- {/if}
- {/if}
- </div>
- </div>
- </div>
- </div>
|