import { createSignal, createEffect, createState, createMemo, createComputed } from "solid-js"; import { req } from '../websocket'; import { pgRange2String, fmt, create_deferred, c } from '../util'; import { state as glo } from '../store'; export default function(props) { let original = { search: '', species: [], types: [], strains: [], effects: [], producers: {}, min_thc2tc: '0', max_thc2tc: '1', sort: 'unsorted', inventory: null }; let america; if(history.state.america) { if(typeof history.state.america.producers === 'object') { america = JSON.parse(JSON.stringify(history.state.america)); for(const key in america.producers) { if(america.producers[key].checked === undefined) { america.producers[key].checked = false; } if(america.producers[key].brands === undefined) { america.producers[key].brands = []; } } } else { america = history.state.america; } } else { let s = new URLSearchParams(location.search); america = {producers: {}}; if(s.has('search')) { america['search'] = s.get('search'); } if(s.has('min_thc2tc')) { america['min_thc2tc'] = s.get('min_thc2tc'); } if(s.has('max_thc2tc')) { america['max_thc2tc'] = s.get('max_thc2tc'); } if(s.has('sort')) { america['sort'] = s.get('sort'); } if(s.has('inventory')) { america['inventory'] = s.get('inventory') === 'true'; } if(s.has('species')) { america['species'] = s.get('species').split('_'); } if(s.has('types')) { america['types'] = s.get('types').split('_'); } if(s.has('strains')) { america['strains'] = s.get('strains').split('_'); } if(s.has('effects')) { america['effects'] = s.get('effects').split('_'); } for(const [key, value] of s) { if(key.startsWith('p_')) { let actual = key.split('_'); if(actual[1]) { america.producers[actual[1]] = {checked: false, brands: value.split('_')} } } } if(s.has('producers')) { let p = s.get('producers').split('_'); for(let i = 0; i < p.length; ++i) { if(america.producers[p[i]] === undefined) { america.producers[p[i]] = {checked: true, brands: []} } else { america.producers[p[i]].checked = true; } } } } const [products, setProducts] = createSignal(history.state.products); const [state, setState] = createState(Object.assign(JSON.parse(JSON.stringify(original)), america)); const [f, setF] = createSignal(history.state.f); createComputed(() => { let o = {}; for(let i = 0; i < f()?.idk.length; ++i) { o[f().idk[i].producer] = {checked: false, brands: []}; if(state.producers[f().idk[i].producer] === undefined) { setState('producers', f().idk[i].producer, o[f().idk[i].producer]); } } original.producers = o; }); let poop = create_deferred(); if(!products()) { lol(); } req('productsq').then(x => {setTimeout(poop.resolve); setF(x);}); createEffect(() => history.replaceState({...history.state, america: diffState()}, "")); createEffect(() => history.replaceState({...history.state, products: products()}, "")); createEffect(() => history.replaceState({...history.state, f: f()}, "")); const inhalation = createMemo(() => state.types.includes('flower') || state.types.includes('preroll') || state.types.includes('cartridge') || state.types.includes('disposable')); const sublingual = createMemo(() => state.types.includes('oil') || state.types.includes('spray')); const ingestion = createMemo(() => state.types.includes('capsule') || state.types.includes('tea') || state.types.includes('gummy') || state.types.includes('mint') || state.types.includes('chocolate') || state.types.includes('baked')); const canSortByConcentration = createMemo(() => { let r = 0; if(inhalation()) {++r} if(sublingual()) {++r} if(ingestion()) {++r} return r === 1; }); const canSortByPrice_g = createMemo(() => inhalation() && !sublingual() && !ingestion()); createEffect(() => { if(!canSortByConcentration() && (state.sort.startsWith('THC') || state.sort.startsWith('CBD')) || !canSortByPrice_g() && state.sort.startsWith('price_g')) { setState('sort', 'unsorted'); } }) function inventoryRadio(e) { let v = null; switch(e.target.value) { case 't': v = true; break; case 'f': v = false; break; } setState('inventory', v) } async function lol() { let conditions = []; for(let key in state.producers) { let a = new Set(state.producers[key].brands); if(a.size) { await poop; } let b; //checking every brand under a producer does not imply checking the producer //the following conditional is ok because productsq is fresh if(state.producers[key].checked || a.size === (b = new Set(f().idk.find(e => e.producer === key || (e.producer === null && key === 'null')).brands)).size && state.producers[key].brands.every(value => b.has(value))) { conditions.push({producer: key === 'null' ? null : key}); } else if(a.size) { conditions.push({producer: key === 'null' ? null : key, brands: state.producers[key].brands}); } } let special = {}; if(state.min_thc2tc !== '0' || state.max_thc2tc !== '1') { special.thc2tc = [state.min_thc2tc, state.max_thc2tc]; } if(state.sort !== 'unsorted') { let [what, how] = state.sort.split(/\s+/); special[what] = how === 'ascending' || how === 'a-z'; } req('products', {...special, stores: [props.sid], species: state.species.length ? state.species : undefined, types: state.types.length ? state.types : undefined, strain_ids: state.strains.length ? state.strains : undefined, producers: conditions.length ? conditions : undefined, search: state.search.length ? state.search : undefined, in_stock: state.inventory === null ? undefined : state.inventory }).then(setProducts); } function diffState() { let diff = {}; for(const key in state) { if(Array.isArray(state[key])) { if(state[key].length) { diff[key] = [...state[key]]; } } else if(typeof state[key] === 'object') { // } else if(state[key] !== original[key]) { diff[key] = state[key]; } } let producers = {}; let yes = false; for(const p in state.producers) { let yesyes = false; let obj = {}; if(state.producers[p].checked) { obj.checked = true; yesyes = true; } if(state.producers[p].brands.length) { obj.brands = [...state.producers[p].brands]; yesyes = true; } if(yesyes) { producers[p] = obj; yes = true; } } if(yes) { diff.producers = producers; } return diff; } return <> <Show when={glo.user?.god && props.sid === undefined}> <a onClick={c} href={'edit'}>⚙</a> </Show> <Show when={f()}> <form onsubmit={e => { e.preventDefault(); lol() let diff = JSON.parse(JSON.stringify(history.state.america)); if(diff.producers) { let a = []; for(const key in diff.producers) { if(diff.producers[key].checked) { a.push(key); } if(diff.producers[key].brands?.length) { diff['p_' + key] = diff.producers[key].brands.join('_'); } } diff.producers = a.join('_'); if(!diff.producers) { delete diff.producers; } } for(const key in diff) { if(Array.isArray(diff[key])) { diff[key] = diff[key].join('_'); } } diff = new URLSearchParams(diff).toString(); if(diff.length) { diff = '?' + diff; } history.replaceState(history.state, "", location.href.split('?')[0] + diff); }}> <label> search <input type="search" value={state.search} onInput={e => setState('search', e.target.value)} /> </label><br /> <label> sort by <select value={state.sort} onChange={e => setState('sort', e.target.value)}> <option>unsorted</option> <option>price ascending</option> <option>price descending</option> <option disabled={!canSortByPrice_g()} title={canSortByPrice_g() ? '' : 'sorting by price / g only makes sense for inhalation products'} value="price_g ascending">minimum price / g ascending</option> <option disabled={!canSortByPrice_g()} title={canSortByPrice_g() ? '' : 'sorting by price / g only makes sense for inhalation products'} value="price_g descending">minimum price / g descending</option> <option disabled={!canSortByConcentration()} title={canSortByConcentration() ? '' : 'sorting by concentration only makes sense for products within a consumption method'}>THC ascending</option> <option disabled={!canSortByConcentration()} title={canSortByConcentration() ? '' : 'sorting by concentration only makes sense for products within a consumption method'}>THC descending</option> <option disabled={!canSortByConcentration()} title={canSortByConcentration() ? '' : 'sorting by concentration only makes sense for products within a consumption method'}>CBD ascending</option> <option disabled={!canSortByConcentration()} title={canSortByConcentration() ? '' : 'sorting by concentration only makes sense for products within a consumption method'}>CBD descending</option> <option>producer a-z</option> <option>producer z-a</option> <option>brand a-z</option> <option>brand z-a</option> <option>name a-z</option> <option>name z-a</option> </select> </label><br /> <fieldset> <legend>inventory</legend> <label><input type="radio" name="inventory" checked={state.inventory === null} onChange={inventoryRadio} value="e" />everything</label><br /> <label><input type="radio" name="inventory" checked={state.inventory} onChange={inventoryRadio} value="t" />in stock</label><br /> <label><input type="radio" name="inventory" checked={state.inventory === false} onChange={inventoryRadio} value="f" />out of stock</label><br /> </fieldset> <fieldset> <legend>species</legend> <For each={f().species}>{s => <> <label><input type="checkbox" checked={state.species.includes(s)} onChange={e => { if(e.target.checked) { setState('species', [s, ...state.species]); } else { setState('species', t => t.filter(sp => sp !== s)); } }} />{s}</label><br /> </>}</For> </fieldset> <fieldset> <legend>formats</legend> <For each={f().product_types}>{s => <> <Switch> <Match when={s === 'flower'}>Inhalation<br/></Match> <Match when={s === 'oil'}>Subligual<br/></Match> <Match when={s === 'capsule'}>Edible<br/></Match> </Switch> <label><input type="checkbox" checked={state.types.includes(s)} onChange={e => { if(e.target.checked) { setState('types', [s, ...state.types]); } else { setState('types', t => t.filter(typ => typ !== s)); } }} />{s}</label><br /> </>}</For> </fieldset> <fieldset> <legend>strains</legend> <div style="height: 300px; overflow-y: auto"> <For each={f().strains}>{s => <> <label><input type="checkbox" checked={state.strains.includes(s.id)} onChange={e => { if(e.target.checked) { setState('strains', [s.id, ...state.strains]); } else { setState('strains', t => t.filter(typ => typ !== s.id)); } }} />{s.name}</label><br /> </>}</For> </div> </fieldset> <fieldset> <legend>producers and brands</legend> <ul style="padding: 0; margin: 0; max-height: 300px; overflow-y: auto"><For each={f().idk}>{s => <li> <label><input type="checkbox" checked={state.producers[s.producer].checked} onChange={e => setState('producers', s.producer, 'checked', e.target.checked)} />{s.producer === null ? 'unknown' : s.producer}</label> <Show when={s.brands.length > 1 || s.brands[0] !== ''}><ul> <For each={s.brands}>{ss => <li> <label><input disabled={state.producers[s.producer].checked} type="checkbox" checked={state.producers[s.producer].brands.includes(ss)} onChange={e => { if(e.target.checked) { setState('producers', s.producer, 'brands', [ss, ...state.producers[s.producer].brands]); } else { setState('producers', s.producer, 'brands', b => b.filter(br => br !== ss)); } }} />{ss === '' ? s.producer : ss}</label> </li>}</For> </ul></Show> </li>}</For></ul> </fieldset> <fieldset> <legend>THC : total cannabinoids</legend> <label>min <input type="number" step="any" min="0" max={state.max_thc2tc} value={state.min_thc2tc} onInput={e => setState('min_thc2tc', e.target.value)} /></label> <label>max <input type="number" step="any" min={state.min_thc2tc} max="1" value={state.max_thc2tc} onInput={e => setState('max_thc2tc', e.target.value)} /></label> </fieldset> <br /> <button>search</button> </form> </Show> <div>{products() && products().length}</div> <div class="grid"> <For each={products()}>{p => <div class="c card"> <div class="front"> <a onClick={c} href={p.product_id + (p.name ? '-' : '') + p.name.replace(/\s+/g, '-') + (p.variant_ids === null ? '' : '?recommended_variants=' + p.variant_ids.join('_'))}> {fmt(p.producer, p.brand, p.name)} </a> <img style="width: 100%; height: auto;" width={p.x} height={p.y} src={p.image} loading="lazy" /> {p.type}<br /> {p.species}<br /> {p.strain}<br /> THC: {pgRange2String(p.thc, p.type, p.min_portions, p.max_portions)}<br /> CBD: {pgRange2String(p.cbd, p.type, p.min_portions, p.max_portions)}<br /> starting from ${p.min_price}{p.min_price_g ? `, ${p.min_price_g} / g` : ''}<br /> <Switch fallback={p.terpenes.slice(1, -1).split(',').join(', ')}> <Match when={p.terpenes === null}>unknown terpenes</Match> <Match when={p.terpenes.length === 0}>terpenes removed</Match> </Switch><br /> </div> <div class="back"> shit can go here eventually need https://github.com/ryansolid/solid/tree/master/packages/solid-rx if we want click instead of hover </div> </div>}</For> </div> </> }