Skip to content

Instantly share code, notes, and snippets.

@sostenesapollo
Created May 14, 2024 15:06
Show Gist options
  • Save sostenesapollo/09c989d0e7addb3c330de952ab50286b to your computer and use it in GitHub Desktop.
Save sostenesapollo/09c989d0e7addb3c330de952ab50286b to your computer and use it in GitHub Desktop.
import axios from "axios"
import { ModalTop } from "~/components/modal-top"
import { notify } from "~/components/snackbar"
import { useAddProductContext } from "./helpers/use-add-product-context"
import CurrencyInput from "~/components/money-field"
import Label from "./label"
import { Input } from "~/components/input"
import { useStockContext } from "../../stock/helpers/use-stock-context"
import { defaultProductData } from "./helpers/use-add-product-context"
import CategorySelection from "./category-selection"
import { formatProductDataToSend } from "./helpers/format-new-product-send"
import { useRef, useState } from "react"
import { base64MimeType, getB64extension } from "~/utils"
import LoadingSpinner from "~/components/loading-spinner"
// import { useDropzone } from 'react-dropzone';
import { Title } from '~/components/Text';
import Divider from "~/components/divider"
import { ButtonWithIcon } from "~/components/button-with-icon"
import ProductSelection from "../add-stock-movement-modal/product-selection"
const { v4: uuid } = require('uuid')
export const convertBase64 = (file: any) => {
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file)
fileReader.onload = () => {
resolve(fileReader.result);
}
fileReader.onerror = (error) => {
reject(error);
}
})
}
export const isImgUrl = (url: string) => {
// Remove query parameters (if any)
const urlWithoutQueryParams = url.split('?')[0];
return /\.(jpg|jpeg|png|webp|avif|gif)$/.test(urlWithoutQueryParams);
}
export const submitImage = async (params: any): Promise<string | null> => {
return new Promise(async (resolve, reject)=>{
const [file, url, base64String, dbType] = params
console.log('SUBMIT IMAGE', file, url, base64String, {dbType});
let b64, imageUrl, filename = 'img.png', type = 'image/png';
if(file) {
b64 = await convertBase64(file[0])
filename = file[0]?.name
type = file[0].type
} else if (url?.includes('http')) {
imageUrl = url
}else if (base64String) {
b64 = base64String
type = base64MimeType(base64String) || 'image/png'
filename = `${uuid}.${getB64extension(base64String || '')}`
}
// return resolve('example.png')
axios.post('/upload-to-s3', { fileBase64: b64, imageUrl, filename, type, id: params.id, databaseType: dbType })
.then(rst=>{
resolve(rst.data)
})
.catch(e=>{
reject()
})
})
}
export const onDropImage = (e: any, _file: any) => {
e?.preventDefault()
let _previewImage;
let _paramsToSubmit = []
if(_file) {
_previewImage = URL.createObjectURL(_file)
_paramsToSubmit = [[_file]]
} else{
const imageUrl = e.dataTransfer.getData('URL')
if(e.dataTransfer.files.length > 0) {
const file = e.dataTransfer.files[0]
if(!isImgUrl(file.name)) {
throw new Error('Imagem inválida, tente outra.')
}
_previewImage = URL.createObjectURL(file)
_paramsToSubmit = [[file]]
// submitImage([file])
} else if(imageUrl.trim()!=='' && imageUrl.includes('http')) {
console.log('IS IMAGE URL');
if(!isImgUrl(imageUrl)) {
throw new Error('Imagem inválida, tente outra.')
}
_paramsToSubmit = [null, imageUrl]
_previewImage = imageUrl
} else if( imageUrl.trim()!=='' && imageUrl.includes('data:') ){
console.log('IS BASE 64');
_paramsToSubmit = [undefined, undefined, imageUrl]
_previewImage = imageUrl
} else {
throw new Error('Imagem inválida, tente outra.')
}
}
return {
_paramsToSubmit,
_previewImage
}
}
export default function Content() {
const { closeModal: _closeModal, newProductData, setNewProductData, inputRef } = useAddProductContext()
const { onKeyDownProduct, previewImage, setPreviewImage, getStockMovements, editProductData } = useStockContext()
const closeModal = () => { _closeModal(); setNewProductData(defaultProductData); }
const onDrop = async (e: any, _file: any)=> {
try {
const result = await onDropImage(e, _file)
setParamsToSubmit(result._paramsToSubmit)
setPreviewImage(result._previewImage)
}catch(e: any){
notify.error(e.message || 'Erro ao selecionar imagem')
}
}
const onSubmit = async () => {
if(loading) return;
if(!newProductData.name) {
return notify.error('É necessário informar um nome')
}
console.log('>', editProductData.image_url);
console.log('>', newProductData.image_url);
const isCreate = !newProductData.id
setLoading(true)
try {
let image;
if(isCreate) {
image = paramsToSubmit.length ? await submitImage(paramsToSubmit) : null
} else if(editProductData.image_url !== newProductData.image_url) {
image = paramsToSubmit.length ? await submitImage(paramsToSubmit) : null
}
axios.post(isCreate ? '/action?table=product&operation=create' : '/action?table=product&operation=update',
formatProductDataToSend({
...newProductData,
image_url: image || newProductData.image_url
})
).then(rst=>{
notify.success(`${isCreate? 'Criado' : 'Atualizado'} com sucesso.`, {position:'bottom-left'})
closeModal()
onKeyDownProduct({target:{value:newProductData?.name }} as any)
setNewProductData(defaultProductData)
getStockMovements()
})
.catch(e=>{
if(e.response.data.message) {
notify.error(e.response.data.message)
} else {
notify.error(`Erro ao ${isCreate? 'criar' : 'atualizar'}`)
}
})
} catch (error) {
notify.error('Erro ao salvar imagem, tente novamente ou escolha outra imagem.')
return;
} finally {
setLoading(false)
}
}
const [updatingImage, setUpdatingImage] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const onDragOver = (e: any) => {
e.preventDefault()
};
const [paramsToSubmit, setParamsToSubmit] = useState<any>([])
return (
<section>
<ModalTop title={ newProductData.id ? 'Atualizar produto' : 'Adicionar produto' } closeModal={closeModal}/>
{loading && (
<div className="flex bg-indigo-200 p-2">
<LoadingSpinner color="blue"/>
<b className="text-indigo-700">
{newProductData.id ? "Atualizando produto..." : "Criando Produto..."}
</b>
</div>
)}
{/* <pre>
data: {JSON.stringify(newProductData, null, 2)}
</pre> */}
<main className="flex flex-col min-h-[50vh]">
<div className="md:flex md:flex-row">
<div className="grow">
<Label text="Nome do produto"/>
<Input
placeholder="Nome do produto"
className="mt-1 mb-1 bg-white"
value={newProductData.name}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, name: e.target.value})}
inputReference={inputRef}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
<div className="grow md:px-2">
<Label text="Modelo"/>
<Input
placeholder="Modelo"
className="mt-1 mb-1 bg-white"
value={newProductData.model}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, model: e.target.value})}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
<div>
<Label text="Código de barras"/>
<Input
placeholder="Código de barras"
className="mt-1 mb-1 bg-white"
value={newProductData.barcode}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, barcode: e.target.value})}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
</div>
<div className="flex flex-col">
<div className="md:flex">
<div className="grow-0">
<Label text="Imagem"/>
{ updatingImage ? (
<b className="text-indigo-600">
Fazendo upload da imagem, aguarde um instante...
</b>
) : (
<b className="text-red-600 pl-2">
{/* Imagem atualizada com sucesso. */}
</b>
)
}
<div>
<div className="flex items-center justify-center w-full">
<label className="flex flex-col items-center justify-center w-full h-30 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-800 hover:bg-gray-600 " style={{backgroundImage: `url(${previewImage || newProductData.image_url})`, backgroundSize: 'contain' }} onDragOver={onDragOver} onDrop={onDrop as any}>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<svg className="w-8 h-8 mb-4 text-gray-300 text-gray-200" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
</svg>
<p className="mb-2 text-sm text-gray-300"><span className="font-semibold">Clique para fazer upload</span> ou arraste e solte</p>
<p className="text-xs text-gray-200">SVG, PNG, JPG or GIF</p>
</div>
<input
id="dropzone-file"
type="file"
className="hidden"
accept="image/*"
onChange={(e: any)=>{
onDrop(null,e.target.files[0] )
}}
style={{background:'red'}}
/>
</label>
</div>
</div>
</div>
<div className="grow md:pl-2 md:pr-2">
<CategorySelection/>
<div>
<Label text="Modelos compatíveis"/>
<Input
placeholder="Modelos compatíveis"
className="mt-1 mb-1 mt-2 bg-white"
value={newProductData.compatible_models}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, compatible_models: e.target.value})}
/>
</div>
</div>
<div className="grow">
<div className="flex flex-row">
<div className="w-2/3 pr-2">
<Label text="Código do produto"/>
<Input
placeholder="Código do produto"
className="mt-2 pt-2 mb-1 bg-white"
value={newProductData.code}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, code: e.target.value})}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
<div className="w-1/3">
<Label text="Unidade"/>
<Input
placeholder="Unidade"
className="mt-2 pt-2 mb-1 bg-white"
value={newProductData.un}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, un: e.target.value})}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
</div>
<div className="flex">
<div className="grow pr-2">
<Label text="Valor de compra"/>
<CurrencyInput
value={newProductData.default_buy_price}
onChange={(value: number)=>setNewProductData({...newProductData, default_buy_price: value})}
className="block w-full md:pr-2 py-3 rounded-md border-0 py-1.5 mt-1 pl-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-md sm:leading-6"
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
<div className="grow">
<Label text="Valor de venda"/>
<CurrencyInput
value={newProductData.price}
onChange={(value: number)=>setNewProductData({...newProductData, price: value})}
className="block w-full py-3 rounded-md border-0 py-1.5 mt-1 pl-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-md sm:leading-6"
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
</div>
</div>
</div>
</div>
</div>
<>
<Divider className="py-2"/>
<Title text="Configurações do produto" className="pb-2" />
<div className="md:flex md:flex-row">
<div className="md:w-1/2">
<div className="grow md:flex md:flex-row">
<div className="grow pt-4">
<input
type="checkbox"
className="w-8 h-8 border-gray-300 rounded cursor-pointer focus:ring-green-600"
checked={newProductData.has_stock}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=> {
setNewProductData({...newProductData, has_stock: e.target.checked } )
}}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
<label htmlFor="candidates" className="pl-1 font-medium text-gray-900">
Produto com estoque
</label>
</div>
{newProductData.has_stock && (
<div className="grow pt-4">
<input
type="checkbox"
className="w-8 h-8 border-gray-300 rounded cursor-pointer focus:ring-green-600"
checked={newProductData.is_returnable}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=> {
setNewProductData({...newProductData, is_returnable: e.target.checked } )
}}
onKeyDown={(e:any)=>{if(e.key === 'Enter') onSubmit()}}
/>
<label htmlFor="candidates" className="pl-1 font-medium text-gray-900">
É retornável
</label>
</div>
)}
</div>
</div>
{!newProductData.id && newProductData.has_stock && (
<div className="md:w-1/2">
<div className="pr-2 flex flex-col">
<Label text="Quantidade inicial no estoque"/>
<Input
placeholder="Quantidade inicial no estoque"
type="number"
className="mt-1 mb-1 mt-2 bg-white"
value={newProductData.stock_initial_quantity}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, stock_initial_quantity: parseFloat(e.target.value) || 0})}
/>
</div>
</div>
)}
{newProductData.has_stock && (
<div className="md:w-1/2">
<div className="pr-2 flex flex-col">
<Label text="Estoque mínimo(Para criar alertas)"/>
<Input
placeholder="Estoque mínimo"
type="number"
className="mt-1 mb-1 mt-2 bg-white"
value={newProductData.minimum_stock}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, minimum_stock: parseFloat(e.target.value)})}
/>
</div>
</div>
)}
</div>
<div className="md:flex">
{newProductData.has_stock && newProductData.is_returnable && (
<div className="md:w-1/2">
<ProductSelection
fieldName="Selecione o vasilhame ou deixe em branco caso seja o líquido."
setSelectedExternal={(v: any)=>setNewProductData({...newProductData, container: v})}
valueExternal={newProductData.container}
className="md:pr-2"
/>
</div>
)}
{newProductData.has_stock && newProductData.is_returnable && (
<div className="md:w-1/2">
<Label text="Nome do produto para ser usado em listagens"/>
<Input
placeholder="Nome do produto para ser usado em listagens ( Completo, ex: P13 )"
className="mt-1 mb-1 pr-2 mt-2 bg-white"
value={newProductData.has_container_product_name}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, has_container_product_name: e.target.value })}
/>
</div>
)}
</div>
</>
<Divider className="py-2"/>
<Title text="Informações tributárias" className="pb-2"/>
<div className="flex flex-row md:w-1/2">
<div className="grow pr-2">
<Label text="NCM"/>
<Input
placeholder="NCM"
className="mt-1 mb-1 bg-white"
value={newProductData.ncmsh}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, ncmsh: e.target.value})}
/>
</div>
<div className="grow pr-2">
<Label text="CFOP"/>
<Input
placeholder="CFOP"
className="mt-1 mb-1 bg-white"
value={newProductData.cfop}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, cfop: e.target.value})}
/>
</div>
<div className="grow">
<Label text="CST"/>
<Input
placeholder="CST"
className="mt-1 mb-1 bg-white"
value={newProductData.cst}
onChange={(e: React.ChangeEvent<HTMLInputElement>)=>setNewProductData({...newProductData, cst: e.target.value})}
/>
</div>
</div>
</main>
{!updatingImage ?
<div className="bottom-0 pt-3 flex">
<div className="flex-1 flex flex-row pr-2 justify-end items-center">
<ButtonWithIcon
title="Fechar"
icon="TimesIcon"
onClick={closeModal}
color="gray"
paddingY="2"
/>
<ButtonWithIcon
onClick={onSubmit}
title="Salvar"
color="indigo"
icon="CheckIcon"
paddingY="2"
/>
</div>
</div> :
'Aguarde um instante enquanto a imagem é salva.'
}
</section>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment