Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c62538dca | |||
| 5bd5a14f15 | |||
| ccea75c99c | |||
| 88a7759aec | |||
| 36e960ef63 | |||
| b910f70462 | |||
| 5174053ffd | |||
| 601099301c | |||
| 53db256244 | |||
| bd4088f949 | |||
| 1427c0d9cc | |||
| 5488559bc9 | |||
| 12279da613 | |||
| 424e62b2c5 | |||
| 0d5c63ebee | |||
| 6e1807d678 | |||
| 556dc38aa9 | |||
| c11f10817d | |||
| 8e9dbe4ba1 | |||
| ebaf5e4aee | |||
| 0bb757abab | |||
| ff4b802d00 | |||
| c84d007125 | |||
| 5725307213 | |||
| c8775f0449 | |||
| 95657481d5 | |||
| fb9d5777ed | |||
| 0dc53adc28 | |||
| 5afbe54268 | |||
| aa7020c19d | |||
| 93b5a8d00c | |||
| ec90bcecb6 | |||
| 2d2f639e36 | |||
| 51f600f2c0 | |||
| be27833716 | |||
| acddfa011c | |||
| bbb9856c64 | |||
| da6159861d | |||
| 4aca08d89a | |||
| c8ed8c2abe | |||
| 19c096bcf6 | |||
| f493d644a5 | |||
| b616b11906 | |||
| 0708e91705 | |||
| 5377bdf0a6 | |||
| 91874d1288 | |||
| 4503b7c33f | |||
| 21865a35d9 | |||
| fcf0ef36ab | |||
| b60c4ae88c | |||
| 29f1191afa | |||
| c9215f456d | |||
| 292d7d8ced | |||
| 3e27d8b650 | |||
| 6947e5ca75 | |||
| c268106579 | |||
| 9891ae452b | |||
| 71d44dd1f9 | |||
| 6123a5532a | |||
| 5aa4055be3 | |||
| 65720e5433 | |||
| 103daee7fb | |||
| ea241bc5dc | |||
| 673cd019f9 | |||
| 245178f0d7 | |||
| f78bedc2a6 | |||
| 46496d7438 | |||
| 08cadfcf33 | |||
| 4645db69dd | |||
| 0a8f7afe3e | |||
| 56ae6196a5 | |||
| 872082ea4a | |||
| 67131615c3 | |||
| 7951052ce3 | |||
| d551657d99 | |||
| 6e94d5e2d5 | |||
| fa7482cd6a | |||
| a8a3cc2b72 | |||
| c36aef9819 | |||
| 4a51269712 | |||
| e6c70e20b9 | |||
| 4050f47bf3 | |||
| 0f9b1657b4 | |||
| e91f39c9e5 | |||
| bc7f73c3a0 | |||
| 602acc14d8 | |||
| 5b39dc1fcc | |||
| 354ac8a242 | |||
| d1f5810635 | |||
| efd202a90b | |||
| 22438b6ef5 | |||
| 245b85b616 | |||
| 84504ada6a | |||
| d90591d501 | |||
| 88841ad738 | |||
| 32806e07bd | |||
| 17432fd705 | |||
| 5c6f2dd117 | |||
| dec5d82187 | |||
| 1755f4cb38 | |||
| ac6e665bb3 | |||
| ea52e63fc6 | |||
| 0243049cf7 | |||
| fb120b5239 | |||
| 7536e54f4a | |||
| ad8ca024ad | |||
| e561b1f342 | |||
| d549e1aaa4 | |||
| cee241604d | |||
| 41dd646e86 | |||
| ebfa8b47f3 | |||
| 1b96716059 | |||
| a95f373971 | |||
| 503349d9d5 | |||
| bfa24de384 | |||
| f0fb5232b9 | |||
| 79b550ae3f | |||
| 5c3e2b89b8 | |||
| 8e3cdf8b17 | |||
| 549e05aeef | |||
| f96a7dc159 | |||
| c3857fce68 | |||
| d0208384ad | |||
| bf857c3652 | |||
| 91f7408244 | |||
| dc50d84d48 | |||
| 670ae0c5e1 | |||
| 74a57800ed | |||
| 80209474fa | |||
| 5d9038e845 | |||
| c59111d15d | |||
| b95794c738 | |||
| d7e74f5e71 | |||
| 84ce71a915 | |||
| 416ce7964d | |||
| 64c0b726ea | |||
| 9607fc9dd5 | |||
| b4a9900efc | |||
| e32f6c2330 | |||
| 90f99cdbdd | |||
| 183339e583 | |||
| 691e9b8841 | |||
| 23f69817c0 | |||
| 4db52ee3c1 | |||
| e4eec833a9 |
11
Taskfile.yml
@@ -16,3 +16,14 @@ tasks:
|
||||
build:
|
||||
cmds:
|
||||
- vendor/bin/dep build
|
||||
ikeaweb01:
|
||||
cmds:
|
||||
- vendor/bin/dep deploy web01.ttx.sk
|
||||
- vendor/bin/dep build web01.ttx.sk
|
||||
nuc:
|
||||
cmds:
|
||||
- vendor/bin/dep deploy nuc.bh.ttx.sk
|
||||
- vendor/bin/dep build nuc.bh.ttx.sk
|
||||
ssh:
|
||||
cmds:
|
||||
- vendor/bin/dep ssh
|
||||
|
||||
@@ -30,9 +30,9 @@ class IkeaPrices extends Command
|
||||
{
|
||||
$article = $this->argument('article');
|
||||
$responses = $countryCompareController->makeRequests($article);
|
||||
$prices = $countryCompareController->processResponse($responses,$article);
|
||||
//dd($prices);
|
||||
return 0;
|
||||
$products = $countryCompareController->processResponse($responses,$article);
|
||||
|
||||
dd($products);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
47
app/Console/Commands/IkeaSitemap.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Spatie\Sitemap\SitemapGenerator;
|
||||
use Spatie\Crawler\Crawler;
|
||||
use Spatie\Sitemap\Tags\Url;
|
||||
use SevenSpan\Matomo\Facades\Matomo;
|
||||
|
||||
class IkeaSitemap extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ikea:sitemap {path}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$path = $this->argument('path');
|
||||
|
||||
SitemapGenerator::create('https://prices.soson.eu')
|
||||
->configureCrawler(function (Crawler $crawler) {
|
||||
$crawler->ignoreRobots();
|
||||
})
|
||||
->getSitemap()
|
||||
->add(Url::create('https://prices.soson.eu/about/')->setPriority(0.7))
|
||||
->add(Url::create('https://prices.soson.eu/exchange/')->setPriority(0.5))
|
||||
->add(Url::create('https://prices.soson.eu/doc/')->setPriority(0.8))
|
||||
->writeToFile($path);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,31 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
use App\Models\CountryCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PHPHtmlParser\Dom;
|
||||
|
||||
class CountryCompareController extends Controller
|
||||
{
|
||||
private $countries = [];
|
||||
|
||||
private $cHash = [];
|
||||
private $dom = null;
|
||||
public function __construct()
|
||||
{
|
||||
$countries = [];
|
||||
CountryCode::all()->each(function ($country) use (&$countries) {
|
||||
$countries[] = $country->country_name;
|
||||
$cHash = [];
|
||||
CountryCode::active('Y')->each(function ($country) use (&$countries, &$cHash) {
|
||||
$countries[] = $country;
|
||||
$cHash[$country->country_name] = $country->country_code;
|
||||
});
|
||||
|
||||
$this->countries = collect($countries);
|
||||
$this->cHash = collect($cHash);
|
||||
$this->dom = new DOMDocument();
|
||||
}
|
||||
public function search(Request $request)
|
||||
{
|
||||
@@ -28,16 +34,20 @@ class CountryCompareController extends Controller
|
||||
$ta_codes = explode(' ', str_replace('.', '', trim($itemCodes)));
|
||||
|
||||
$requests = $this->makeRequests($ta_codes[0]);
|
||||
return $this->processResponse($requests,$ta_codes[0]);
|
||||
return $this->processResponse($requests, $ta_codes[0]);
|
||||
}
|
||||
|
||||
public function compare($countries, $cHash, $aCodes)
|
||||
{
|
||||
$requests = $this->makeRequests($aCodes);
|
||||
return $this->processResponse($requests, $aCodes);
|
||||
}
|
||||
public function makeRequests($ta_code)
|
||||
{
|
||||
$requests = Http::pool(fn (Pool $pool) => [
|
||||
CountryCode::all()->each(function ($countryCode) use ($pool, $ta_code) {
|
||||
$this->countries[] = $countryCode->country_name;
|
||||
Log::info('Getting page {url}', [ 'url' => $countryCode->search_url . $ta_code ] );
|
||||
return $pool->as($countryCode->country_name)->get($countryCode->search_url . $ta_code);
|
||||
$this->countries->each(function ($country) use ($pool, $ta_code) {
|
||||
Log::info('Getting page {url}', ['url' => $country->search_url . $ta_code[0]]);
|
||||
return $pool->as($country->country_code)->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36')->get($country->search_url . $ta_code[0]);
|
||||
})
|
||||
]);
|
||||
return $requests;
|
||||
@@ -45,52 +55,64 @@ class CountryCompareController extends Controller
|
||||
|
||||
public function makeRequest($ta_code, $country)
|
||||
{
|
||||
$countryCode = CountryCode::where('country_name', $country)->first();
|
||||
$country = CountryCode::where('country_name', $country)->first();
|
||||
return Http::pool(fn (Pool $pool) => [
|
||||
$pool->as($countryCode->country_name)->get($countryCode->search_url . $ta_code)
|
||||
$pool->as($country->country_name)->get($country->search_url . $ta_code)
|
||||
]);
|
||||
}
|
||||
|
||||
public function processResponse($responses, $code)
|
||||
{
|
||||
$code = $code[0];
|
||||
$prices = [];
|
||||
|
||||
foreach ($this->countries as $country) {
|
||||
|
||||
if (isset($responses[$country]) && $responses[$country]->ok()) {
|
||||
foreach ($this->countries as $c) {
|
||||
|
||||
$response = $responses[$country]->body();
|
||||
$country = $c->country_code;
|
||||
|
||||
if ($responses[$country] instanceof \Illuminate\Http\Client\Response && $responses[$country]->ok()) {
|
||||
|
||||
// dd($responses);
|
||||
$response = (string) $responses[$country]->body();
|
||||
//dd($response)
|
||||
switch ($country) {
|
||||
case "Bulgaria":
|
||||
case "Cyprus":
|
||||
case "Greece":
|
||||
$prices[$country] = $this->parseJson_CY_GR_BG($code, (string) $response);
|
||||
case "BG":
|
||||
case "CY":
|
||||
case "GR":
|
||||
$prices[$country] = $this->parseJson_CY_GR_BG($code, (string) $response, $country);
|
||||
break;
|
||||
case "Iceland":
|
||||
$prices[$country] = $this->parseJson_IS($country, $code, (string) $response);
|
||||
case "IS":
|
||||
$prices[$country] = $this->parseJson_IS($code, (string) $response, $country);
|
||||
break;
|
||||
case "Türkiye":
|
||||
$prices[$country] = $this->parseJson_TR((string) $response);
|
||||
case "TR":
|
||||
$prices[$country] = $this->parseJson_TR($code, (string) $response, $country);
|
||||
break;
|
||||
case "Lithuania":
|
||||
case "Estonia":
|
||||
case "Latvia":
|
||||
$prices[$country] = $this->parseJson_EE_LT_LV($country, $code, (string) $response);
|
||||
case "EE":
|
||||
case "LT":
|
||||
case "LV":
|
||||
$prices[$country] = $this->parseJson_EE_LT_LV($code, (string) $response, $country);
|
||||
break;
|
||||
default:
|
||||
$prices[$country] = $this->parseJson((string) $response);
|
||||
$prices[$country] = $this->parseJson($code, (string) $response, $country);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $prices;
|
||||
$products = [];
|
||||
collect($prices)->each(function ($price) use (&$products) {
|
||||
if (count($price))
|
||||
$products[] = $price;
|
||||
});
|
||||
return collect($products);
|
||||
}
|
||||
public function parseJson($json_raw)
|
||||
|
||||
public function parseJson($code, $json_raw, $country)
|
||||
{
|
||||
$json_values = array();
|
||||
|
||||
Log::info('{json_raw} {country}', [$json_raw, $country]);
|
||||
$json_decoded = json_decode($json_raw, true);
|
||||
//dd($json_decoded);
|
||||
|
||||
$searchResultPage = $json_decoded["searchResultPage"];
|
||||
$products = $searchResultPage["products"];
|
||||
$main = $products["main"];
|
||||
@@ -103,10 +125,11 @@ class CountryCompareController extends Controller
|
||||
$numeral = $salesPrice["numeral"];
|
||||
|
||||
$json_values = array(
|
||||
'url_product' => $product['pipUrl'],
|
||||
'code' => $code,
|
||||
'url' => $product['pipUrl'],
|
||||
'tag' => $product['tag'],
|
||||
'price' => $numeral,
|
||||
'price_eur' => 0
|
||||
'country' => $country,
|
||||
'salesPrice' => $numeral,
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -114,99 +137,96 @@ class CountryCompareController extends Controller
|
||||
return $json_values;
|
||||
}
|
||||
|
||||
public function parseJson_TR($body)
|
||||
public function parseJson_TR($code, $body, $country)
|
||||
{
|
||||
//echo "country: " . $country . ", code: " . $code . "<br>";
|
||||
|
||||
$dochtml = new \DOMDocument();
|
||||
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
|
||||
$dochtml = new DOMDocument();
|
||||
@$dochtml->loadHTML($body, LIBXML_NOERROR);
|
||||
|
||||
$xpath = new \DOMXpath($dochtml);
|
||||
@$xpath = new DOMXpath($dochtml);
|
||||
$price = null;
|
||||
//$c = ltrim($code, "0");
|
||||
$json_values = array();
|
||||
|
||||
$url_product = $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
|
||||
$price = $xpath->query('/html/head/meta[(@property="og:price:amount")]/@content')[0]->nodeValue;
|
||||
@$url_product = $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
|
||||
@$price = $xpath->query('/html/head/meta[(@property="og:price:amount")]/@content')[0]->nodeValue;
|
||||
|
||||
$price = floatval(trim(str_replace('.', '', $price)));
|
||||
|
||||
$json_values = array(
|
||||
'url_product' => $url_product,
|
||||
'code' => $code,
|
||||
'url' => $url_product,
|
||||
'tag' => null,
|
||||
'price' => floatval($price),
|
||||
'price_eur' => 0
|
||||
'salesPrice' => floatval($price),
|
||||
'country' => $country,
|
||||
);
|
||||
|
||||
return $json_values;
|
||||
}
|
||||
|
||||
public function parseJson_CY_GR_BG($code, $body)
|
||||
public function parseJson_CY_GR_BG($code, $body, $country)
|
||||
{
|
||||
//echo "country: " . $country . ", code: " . $code . "<br>";
|
||||
//Log::info('{country},{body},{code}',[$country, $body, $code]);
|
||||
//$body = file_get_contents("https://www.ikea.bg/search-results/?query=80366284");
|
||||
libxml_use_internal_errors(true);
|
||||
$dochtml = new DOMDocument();
|
||||
@$dochtml->loadHTML($body, LIBXML_NOERROR);
|
||||
|
||||
$dochtml = new \DOMDocument();
|
||||
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
|
||||
|
||||
$xpath = new \DOMXpath($dochtml);
|
||||
@$xpath = new DOMXpath($dochtml);
|
||||
$price = null;
|
||||
$c = ltrim($code, "0");
|
||||
$json_values = array();
|
||||
|
||||
try {
|
||||
$price = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-price')[0]->nodeValue;
|
||||
$url_product = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-url')[0]->nodeValue;
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
@$price = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-price')[0]->nodeValue;
|
||||
@$url_product = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-url')[0]->nodeValue;
|
||||
|
||||
$price = floatval($price);
|
||||
|
||||
|
||||
$json_values = array(
|
||||
'url_product' => $url_product,
|
||||
'code' => $code,
|
||||
'url' => $url_product,
|
||||
'tag' => null,
|
||||
'price' => floatval($price),
|
||||
'price_eur' => 0
|
||||
'salesPrice' => floatval($price),
|
||||
'country' => $country,
|
||||
);
|
||||
|
||||
libxml_clear_errors();
|
||||
return $json_values;
|
||||
}
|
||||
|
||||
public function parseJson_EE_LT_LV($country, $code, $body)
|
||||
public function parseJson_EE_LT_LV($code, $body, $country)
|
||||
{
|
||||
//echo "country: " . $country . ", code: " . $code . "<br>";
|
||||
|
||||
$dochtml = new \DOMDocument();
|
||||
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
|
||||
$dochtml = new DOMDocument();
|
||||
@$dochtml->loadHTML($body);
|
||||
|
||||
$xpath = new \DOMXpath($dochtml);
|
||||
@$xpath = new DOMXpath($dochtml);
|
||||
$price = null;
|
||||
$c = ltrim($code, "0");
|
||||
$json_values = array();
|
||||
|
||||
try {
|
||||
$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//p[@class="itemNormalPrice display-6"]/span')[0]->nodeValue;
|
||||
@$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//p[@class="itemNormalPrice display-6"]/span')[0]->nodeValue;
|
||||
|
||||
if (is_null($price) || empty($price)) {
|
||||
$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//div[@class="itemBTI display-6"]/span')[0]->nodeValue;
|
||||
@$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//div[@class="itemBTI display-6"]/span')[0]->nodeValue;
|
||||
}
|
||||
|
||||
switch ($country) {
|
||||
case "Estonia":
|
||||
$url_product = "https://www.ikea.ee" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
case "EE":
|
||||
@$url_product = "https://www.ikea.ee" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
break;
|
||||
case "Lithuania":
|
||||
$url_product = "https://www.ikea.lt" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
case "LT":
|
||||
@$url_product = "https://www.ikea.lt" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
break;
|
||||
case "Latvia":
|
||||
$url_product = "https://www.ikea.lv" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
case "LV":
|
||||
@$url_product = "https://www.ikea.lv" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
|
||||
break;
|
||||
default:
|
||||
$url_product = null;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_null($price) || empty($price)) {
|
||||
$url_product = null;
|
||||
@@ -215,36 +235,36 @@ class CountryCompareController extends Controller
|
||||
$price = floatval(trim(str_replace(' ', '', str_replace(',', '.', str_replace(array('€'), '', $price)))));
|
||||
|
||||
$json_values = array(
|
||||
'url_product' => $url_product,
|
||||
//$url_product,
|
||||
'code' => $code,
|
||||
'url' => $url_product,
|
||||
'tag' => null,
|
||||
'price' => $price,
|
||||
'price_eur' => 0
|
||||
'salesPrice' => $price,
|
||||
'country' => $country,
|
||||
);
|
||||
|
||||
return $json_values;
|
||||
}
|
||||
|
||||
public function parseJson_IS($country, $code, $body)
|
||||
public function parseJson_IS($code, $body, $country)
|
||||
{
|
||||
//echo "country: " . $country . ", code: " . $code . "<br>";
|
||||
|
||||
$dochtml = new \DOMDocument();
|
||||
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
|
||||
$dochtml = new DOMDocument();
|
||||
@$dochtml->loadHTML($body);
|
||||
|
||||
$xpath = new \DOMXpath($dochtml);
|
||||
|
||||
@$xpath = new DOMXpath($dochtml);
|
||||
$price = null;
|
||||
$c = ltrim($code, "0");
|
||||
$json_values = array();
|
||||
|
||||
try {
|
||||
$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemNormalPrice revamp_price price"]/span/span')[0]->nodeValue;
|
||||
@$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemNormalPrice revamp_price price"]/span/span')->item(0)->nodeValue;
|
||||
|
||||
if (is_null($price) || empty($price)) {
|
||||
$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemBTI display-6 revamp_price price"]/span/span')[0]->nodeValue;
|
||||
@$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemBTI display-6 revamp_price price"]/span/span')->item(0)->nodeValue;
|
||||
}
|
||||
|
||||
$url_product = "https://www.ikea.is" . $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
|
||||
@$url_product = "https://www.ikea.is" . $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
|
||||
|
||||
//echo "url_product: " . $url_product . "<br>";
|
||||
|
||||
@@ -252,19 +272,17 @@ class CountryCompareController extends Controller
|
||||
$url_product = null;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
$price = floatval(trim(str_replace(' ', '', str_replace(',', '.', str_replace('.', '', str_replace(array('€'), '', $price))))));
|
||||
|
||||
$json_values = array(
|
||||
'url_product' => $url_product,
|
||||
//$url_product,
|
||||
'code' => $code,
|
||||
'url' => $url_product,
|
||||
'tag' => null,
|
||||
'price' => $price,
|
||||
'price_eur' => 0
|
||||
'salesPrice' => $price,
|
||||
'country' => $country,
|
||||
);
|
||||
|
||||
return $json_values;
|
||||
}
|
||||
}
|
||||
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },
|
||||
|
||||
28
app/Http/Controllers/FeedbackController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Mail\SendFeedbackMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class FeedbackController extends Controller
|
||||
{
|
||||
|
||||
public function sendEmail(Request $request)
|
||||
{
|
||||
$name = $request->input('name');
|
||||
$email = $request->input('email');
|
||||
$subject = $request->input('subject');
|
||||
$message = $request->input('message');
|
||||
|
||||
$data = ['name' => $name,'email'=> $email,'message'=> $message, 'subject' => $subject ];
|
||||
|
||||
Mail::send('mails.feedback', [ 'data' => $data ], function($message) {
|
||||
$message->to('jaro@ttx.sk', 'Jaroslv Drzik')
|
||||
->to('marosholub@gmail.com','Maros Holub')
|
||||
->subject('IKEA Feedback Mail');
|
||||
});
|
||||
|
||||
return response()->json(['success' => 'Send email successfully.']);
|
||||
}
|
||||
}
|
||||
14
app/Http/Controllers/GeoIPController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
use App\Models\IkeaProducts;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
class GeoIPController extends Controller
|
||||
{
|
||||
public function index(Request $request, $ip = null)
|
||||
{
|
||||
return geoip($ip)->getLocation()->toArray();
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,12 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class IkeaProductsController extends Controller
|
||||
{
|
||||
public function search(Request $request, string $item, string $country = '')
|
||||
public function search(Request $request, string $field, string $text, ?string $country = '')
|
||||
{
|
||||
return IkeaProducts::search($item, $country);
|
||||
return IkeaProducts::search($field, $text, $country);
|
||||
}
|
||||
public function multi_search(Request $request, string $item, ?string $desc='', ?string $code='', ?string $country = '')
|
||||
{
|
||||
return IkeaProducts::search($item,$desc,$code,$country);
|
||||
}
|
||||
}
|
||||
|
||||
35
app/Http/Controllers/LoggingController.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\DataTableRequest;
|
||||
use App\Models\Logging;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
class LoggingController extends Controller
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function logging()
|
||||
{
|
||||
return Inertia::render('Superuser/Activity/Logging');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \App\Http\Requests\DataTableRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function records(DataTableRequest $request)
|
||||
{
|
||||
$request->validated();
|
||||
|
||||
$rec = Logging::today()->select(['*'])->paginate($request->per_page ?: 30);
|
||||
|
||||
Log::info("message",["rec" => $rec]);
|
||||
|
||||
return $rec;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
//use Alfrasc\MatomoTracker\Facades\LaravelMatomoTracker;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\IkeaProducts;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
@@ -10,16 +11,29 @@ use Illuminate\Support\Facades\Log;
|
||||
use App\Models\CountryCode;
|
||||
use App\Models\CurrencyRates;
|
||||
use Inertia\Inertia;
|
||||
use App\Http\Controllers\CountryCompareController;
|
||||
|
||||
class ProductsCompareController extends Controller
|
||||
{
|
||||
private $countryCompareController;
|
||||
public function __construct(CountryCompareController $countryCompareController)
|
||||
{
|
||||
$this->countryCompareController = $countryCompareController;
|
||||
}
|
||||
public function compare(Request $request)
|
||||
{
|
||||
$codes = $request->input("codes");
|
||||
$countries = $request->input("countries");
|
||||
$currency = $request->input("currency");
|
||||
$online = $request->input("online");
|
||||
$text = $request->input("text");
|
||||
$country = $request->input("country");
|
||||
|
||||
$vars = ["codes" => $codes, "countries" => $countries, "country" => $country, "online" => $online, "text" => $text, "ip" => $request->ip(), "referer" => $request->headers->get('referer')];
|
||||
|
||||
Log::channel('db')->info("{codes} {countries} {country} {online} {text} {ip} {referer}", $vars );
|
||||
//LaravelMatomoTracker::doTrackPageView('compare:'. $text);
|
||||
|
||||
|
||||
Log::info("{codes} {countries}", ["codes" => $codes, "countries" => $countries]);
|
||||
|
||||
$codes = collect($codes);
|
||||
$countries = collect($countries);
|
||||
@@ -27,7 +41,7 @@ class ProductsCompareController extends Controller
|
||||
if ($countries != null && count($countries)) {
|
||||
$hCountry = CountryCode::countryHash();
|
||||
$countries = $countries->map(function ($country) use ($hCountry) {
|
||||
return $hCountry[$country];
|
||||
return $hCountry[$country["name"]];
|
||||
});
|
||||
} else {
|
||||
$countries = [];
|
||||
@@ -35,31 +49,29 @@ class ProductsCompareController extends Controller
|
||||
|
||||
$cHash = CountryCode::code_countryHash();
|
||||
if (is_array($codes) == false)
|
||||
$aCodes = [$codes['code']];
|
||||
$aCodes = $codes;
|
||||
else
|
||||
$aCodes = $codes->map(function ($code) {
|
||||
return $code['code'];
|
||||
return $code;
|
||||
});
|
||||
|
||||
$products = IkeaProducts::whereIn("code", $aCodes);
|
||||
if (count($countries)) $products->whereIn("country",$countries);
|
||||
$products = $products->get();
|
||||
|
||||
$currencyRates = CurrencyRates::rates2EUR("Y");
|
||||
if ($currency == "EUR") {
|
||||
$coef = 1;
|
||||
if ($online == true) {
|
||||
Log::info("ONLINE {codes} {countries}", ["codes" => $codes, "countries" => $countries, "online" => $online]);
|
||||
$products = $this->countryCompareController->compare($countries, $cHash, $codes);
|
||||
} else {
|
||||
$coef = floatval(CurrencyRates::where('currency',$currency)->first()->rate);
|
||||
$products = IkeaProducts::whereIn("itemNoGlobal", $aCodes);
|
||||
if (count($countries)) $products->whereIn("country", $countries);
|
||||
$products = $products->get();
|
||||
}
|
||||
|
||||
$products = $products->map(function ($product) use ($currencyRates, $coef) {
|
||||
$product["salesPrice"] = round(floatval(($product["salesPrice"]) / $currencyRates[$product["country"]]) * $coef, 2);
|
||||
$products = $products->map(function ($product) use ($cHash) {
|
||||
$product["countryName"] = $cHash[$product["country"]];
|
||||
return $product;
|
||||
});
|
||||
Log::info("{products}", ["products" => $products]);
|
||||
return Inertia::render('IkeaRoot', [
|
||||
return [
|
||||
'products' => $products,
|
||||
'countryHash' => $cHash,
|
||||
]);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
13
app/Http/Controllers/ProductsCountController.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
use App\Models\ProductsCount;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductsCountController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return ProductsCount::all();
|
||||
}
|
||||
}
|
||||
24
app/Http/Controllers/SettingsController.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Settings;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return Settings::all()->pluck("value","name")->all();
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
return Settings::get($name);
|
||||
}
|
||||
}
|
||||
38
app/Mail/SendFeedbackMail.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
|
||||
|
||||
class SendFeedbackMail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $data;
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this->subject('IKEA Feedback Mail')->cc('')->view('mails.feedback')->with(['data' => $this->data]);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class IkeaProducts extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
|
||||
protected $table = 't_ikea_products';
|
||||
|
||||
/**
|
||||
@@ -24,22 +25,40 @@ class IkeaProducts extends Model
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
protected function search($item,$country = '')
|
||||
protected function search($field, $text, $country)
|
||||
{
|
||||
if (strlen($item) >= 2 && strlen($item) < 5) {
|
||||
$result = $this->where('name', 'LIKE', $item . '%');
|
||||
if ($country != '') $result = $result->where('country',$country);
|
||||
if ((strlen($text) >= 2 && $field == "name") || (strlen($text) > 3 && $field == 'typeName') || (strlen($text) == 8 && $field == 'code')) {
|
||||
$result = $this->where($field, 'LIKE', '%' . $text . '%');
|
||||
if ($country != '') {
|
||||
$result->where('country', $country);
|
||||
}
|
||||
return $result->get();
|
||||
} else if (strlen($item >= 5)) {
|
||||
$result = $this->where(function ($query) use ($item) { $query->where('typeName', 'LIKE', '%' . $item . '%')->orWhere('name', 'LIKE', '%' . $item . '%'); } );
|
||||
if ($country != '') $result = $result->where('country',$country);
|
||||
} else if ($field == "all") {
|
||||
if (preg_match("/0[0-9]+/", $text)) {
|
||||
$result = $this->where("code", '=',$text);
|
||||
$result->where('country', $country);
|
||||
} else {
|
||||
if (strlen($text) < 2) return [];
|
||||
$result = $this;
|
||||
|
||||
return $result->get();
|
||||
$result = $this->where(function ($query) use ($text) {
|
||||
if (strlen($text) >= 2) $query->where("name", 'LIKE', '%'. $text . '%');
|
||||
if (strlen($text) > 3) $query->orWhere("typeName", 'LIKE', '%'. $text . '%');
|
||||
});
|
||||
|
||||
if ($country != '') {
|
||||
$result->where('country', $country);
|
||||
}
|
||||
}
|
||||
return $result->get();
|
||||
}
|
||||
|
||||
protected function multisearch($codes, $countries) {
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function multisearch($codes, $countries)
|
||||
{
|
||||
//$countries = $
|
||||
// return $this->where('code',$codes)->where('country',$countries);
|
||||
}
|
||||
|
||||
21
app/Models/Logging.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Logging extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'log_messages';
|
||||
|
||||
protected function today() {
|
||||
return $this->whereDate('logged_at',Carbon::today());
|
||||
}
|
||||
}
|
||||
40
app/Models/ProductsCount.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProductsCount extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 't_products_count';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
//protected $primaryKey = 'name';
|
||||
/**
|
||||
* Indicates if the model's ID is auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected function get()
|
||||
{
|
||||
return $this->get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
40
app/Models/Settings.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Settings extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'c_settings';
|
||||
|
||||
/**
|
||||
* The primary key associated with the table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
//protected $primaryKey = 'name';
|
||||
/**
|
||||
* Indicates if the model's ID is auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
use HasFactory;
|
||||
|
||||
protected function get($name = null)
|
||||
{
|
||||
return $this->where('name',$name)->get();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -16,10 +16,14 @@
|
||||
"laravel/sanctum": "^2.14.1",
|
||||
"laravel/tinker": "^2.7",
|
||||
"masterminds/html5": "^2.8",
|
||||
"matomo/matomo-php-tracker": "^3.2",
|
||||
"paquettg/php-html-parser": "^2.2",
|
||||
"pusher/pusher-php-server": "^7.0",
|
||||
"spatie/laravel-permission": "^5.5",
|
||||
"tightenco/ziggy": "^1.0"
|
||||
"spatie/laravel-sitemap": "^6.4",
|
||||
"tightenco/ziggy": "^1.0",
|
||||
"torann/geoip": "^3.0",
|
||||
"yoeriboven/laravel-log-db": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.7",
|
||||
|
||||
2295
composer.lock
generated
@@ -69,7 +69,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'timezone' => 'Asia/Jakarta',
|
||||
'timezone' => 'Europe/Bratislava',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -198,6 +198,7 @@ return [
|
||||
App\Providers\JetstreamServiceProvider::class,
|
||||
|
||||
\hisorange\BrowserDetect\ServiceProvider::class,
|
||||
\Torann\GeoIP\GeoIPServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
@@ -213,6 +214,8 @@ return [
|
||||
|
||||
'aliases' => Facade::defaultAliases()->merge([
|
||||
// 'ExampleClass' => App\Example\ExampleClass::class,
|
||||
'GeoIP' => \Torann\GeoIP\Facades\GeoIP::class,
|
||||
'Vite' => \Illuminate\Support\Facades\Vite::class,
|
||||
])->toArray(),
|
||||
|
||||
];
|
||||
|
||||
165
config/geoip.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Logging Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure the log settings for when a location is not found
|
||||
| for the IP provided.
|
||||
|
|
||||
*/
|
||||
|
||||
'log_failures' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Include Currency in Results
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When enabled the system will do it's best in deciding the user's currency
|
||||
| by matching their ISO code to a preset list of currencies.
|
||||
|
|
||||
*/
|
||||
|
||||
'include_currency' => true,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Service
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the default storage driver that should be used
|
||||
| by the framework.
|
||||
|
|
||||
| Supported: "maxmind_database", "maxmind_api", "ipapi"
|
||||
|
|
||||
*/
|
||||
|
||||
'service' => 'ipapi',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Storage Specific Configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure as many storage drivers as you wish.
|
||||
|
|
||||
*/
|
||||
|
||||
'services' => [
|
||||
|
||||
'maxmind_database' => [
|
||||
'class' => \Torann\GeoIP\Services\MaxMindDatabase::class,
|
||||
'database_path' => storage_path('app/geoip.mmdb'),
|
||||
'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')),
|
||||
'locales' => ['en'],
|
||||
],
|
||||
|
||||
'maxmind_api' => [
|
||||
'class' => \Torann\GeoIP\Services\MaxMindWebService::class,
|
||||
'user_id' => env('MAXMIND_USER_ID'),
|
||||
'license_key' => env('MAXMIND_LICENSE_KEY'),
|
||||
'locales' => ['en'],
|
||||
],
|
||||
|
||||
'ipapi' => [
|
||||
'class' => \Torann\GeoIP\Services\IPApi::class,
|
||||
'secure' => true,
|
||||
'key' => env('IPAPI_KEY'),
|
||||
'continent_path' => storage_path('app/continents.json'),
|
||||
'lang' => 'en',
|
||||
],
|
||||
|
||||
'ipgeolocation' => [
|
||||
'class' => \Torann\GeoIP\Services\IPGeoLocation::class,
|
||||
'secure' => true,
|
||||
'key' => env('IPGEOLOCATION_KEY'),
|
||||
'continent_path' => storage_path('app/continents.json'),
|
||||
'lang' => 'en',
|
||||
],
|
||||
|
||||
'ipdata' => [
|
||||
'class' => \Torann\GeoIP\Services\IPData::class,
|
||||
'key' => env('IPDATA_API_KEY'),
|
||||
'secure' => true,
|
||||
],
|
||||
|
||||
'ipfinder' => [
|
||||
'class' => \Torann\GeoIP\Services\IPFinder::class,
|
||||
'key' => env('IPFINDER_API_KEY'),
|
||||
'secure' => true,
|
||||
'locales' => ['en'],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Cache Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify the type of caching that should be used
|
||||
| by the package.
|
||||
|
|
||||
| Options:
|
||||
|
|
||||
| all - All location are cached
|
||||
| some - Cache only the requesting user
|
||||
| none - Disable cached
|
||||
|
|
||||
*/
|
||||
|
||||
'cache' => 'all',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Tags
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Cache tags are not supported when using the file or database cache
|
||||
| drivers in Laravel. This is done so that only locations can be cleared.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache_tags' => ['torann-geoip-location'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Cache Expiration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Define how long cached location are valid.
|
||||
|
|
||||
*/
|
||||
|
||||
'cache_expires' => 30,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Location
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Return when a location is not found.
|
||||
|
|
||||
*/
|
||||
|
||||
'default_location' => [
|
||||
'ip' => '127.0.0.0',
|
||||
'iso_code' => 'US',
|
||||
'country' => 'United States',
|
||||
'city' => 'New Haven',
|
||||
'state' => 'CT',
|
||||
'state_name' => 'Connecticut',
|
||||
'postal_code' => '06510',
|
||||
'lat' => 41.31,
|
||||
'lon' => -72.92,
|
||||
'timezone' => 'America/New_York',
|
||||
'continent' => 'NA',
|
||||
'default' => true,
|
||||
'currency' => 'USD',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -3,6 +3,7 @@
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
use Yoeriboven\LaravelLogDb\DatabaseLogger;
|
||||
|
||||
return [
|
||||
|
||||
@@ -117,6 +118,11 @@ return [
|
||||
'emergency' => [
|
||||
'path' => storage_path('logs/laravel.log'),
|
||||
],
|
||||
|
||||
'db' => [
|
||||
'driver' => 'custom',
|
||||
'via' => DatabaseLogger::class,
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -36,9 +36,9 @@ return [
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'host' => env('MAIL_HOST', 'localhost'),
|
||||
'port' => env('MAIL_PORT', 25),
|
||||
'encryption' => null,
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
|
||||
29
config/matomotracker.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/**
|
||||
* The URL of your Matomo install
|
||||
*/
|
||||
'url' => env('MATOMO_URL', 'https://matomo.soson.eu'),
|
||||
|
||||
/**
|
||||
* The id of the site that should be tracked
|
||||
*/
|
||||
'idSite' => env('MATOMO_SITE_ID', 3),
|
||||
|
||||
/**
|
||||
* The auth token of your user
|
||||
*/
|
||||
'tokenAuth' => env('MATOMO_AUTH_TOKEN', '3a58110f13366a6438f86a17bf6d2d4c'),
|
||||
|
||||
/**
|
||||
* For queuing the tracking you can use custom queue names. Use 'default' if you want to run the queued items within the standard queue.
|
||||
*/
|
||||
'queue' => env('MATOMO_QUEUE', 'matomotracker'),
|
||||
|
||||
/**
|
||||
* Optionally set a custom queue connection. Laravel defaults to "sync".
|
||||
*/
|
||||
'queueConnection' => env('MATOMO_QUEUE_CONNECTION', 'default'),
|
||||
];
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('log_messages', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('level_name');
|
||||
$table->unsignedSmallInteger('level');
|
||||
$table->string('message');
|
||||
$table->dateTime('logged_at');
|
||||
$table->json('context');
|
||||
$table->json('extra');
|
||||
});
|
||||
}
|
||||
};
|
||||
18
deploy.yaml
@@ -1,5 +1,6 @@
|
||||
import:
|
||||
- recipe/laravel.php
|
||||
- contrib/php-fpm.php
|
||||
|
||||
config:
|
||||
repository: 'https://git.bh.ttx.sk/jaro/ikea/'
|
||||
@@ -7,13 +8,26 @@ config:
|
||||
hosts:
|
||||
web01.ttx.sk:
|
||||
remote_user: git
|
||||
branch: newlook
|
||||
deploy_path: '/var/www/html/ikea'
|
||||
nuc.bh.ttx.sk:
|
||||
remote_user: deployer
|
||||
branch: newlook
|
||||
deploy_path: '/var/www/html/ikea'
|
||||
bin/php: /usr/bin/php81
|
||||
php_fpm_version: 8.1
|
||||
php_fpm_service: php81-php-fpm
|
||||
after:
|
||||
deploy: php-fpm:reload
|
||||
|
||||
|
||||
|
||||
|
||||
tasks:
|
||||
build:
|
||||
- cd: "/var/www/html/ikea/current"
|
||||
- run: "yarn install"
|
||||
- run: "yarn build"
|
||||
- run: "pnpm install"
|
||||
- run: "pnpm run build"
|
||||
|
||||
after:
|
||||
deploy:failed: deploy:unlock
|
||||
|
||||
35
lang/id.json
@@ -109,5 +109,38 @@
|
||||
"referenceerror: currency is not defined": "ReferenceError: currency is not defined",
|
||||
"typeerror: can't access property \"unshift\", currency.velue is undefined": "TypeError: can't access property \"unshift\", currency.velue is undefined",
|
||||
"item must be selected": "Item must be selected",
|
||||
"typeerror: can't access property \"code\", form.country.value is undefined": "TypeError: can't access property \"code\", form.country.value is undefined"
|
||||
"typeerror: can't access property \"code\", form.country.value is undefined": "TypeError: can't access property \"code\", form.country.value is undefined",
|
||||
"referenceerror: item is not defined": "ReferenceError: item is not defined",
|
||||
"error: ziggy error: route 'products.search.multiple' is not in the route list.": "Error: Ziggy error: route 'products.search.multiple' is not in the route list.",
|
||||
"error: ziggy error: route 'products.msearch' is not in the route list.": "Error: Ziggy error: route 'products.msearch' is not in the route list.",
|
||||
"error: request failed with status code 404": "Error: Request failed with status code 404",
|
||||
"error: ziggy error: object passed as 'item' parameter is missing route model binding key 'undefined'.": "Error: Ziggy error: object passed as 'item' parameter is missing route model binding key 'undefined'.",
|
||||
"error: ziggy error: object passed as 'field' parameter is missing route model binding key 'undefined'.": "Error: Ziggy error: object passed as 'field' parameter is missing route model binding key 'undefined'.",
|
||||
"referenceerror: assignment to undeclared variable country": "ReferenceError: assignment to undeclared variable country",
|
||||
"typeerror: can't access property \"map\", ccountry.value is undefined": "TypeError: can't access property \"map\", ccountry.value is undefined",
|
||||
"referenceerror: priceunit is not defined": "ReferenceError: priceUnit is not defined",
|
||||
"referenceerror: mainimagealt is not defined": "ReferenceError: mainImageAlt is not defined",
|
||||
"referenceerror: countrycurrency is not defined": "ReferenceError: countryCurrency is not defined",
|
||||
"referenceerror: currencycountryrate is not defined": "ReferenceError: currencyCountryRate is not defined",
|
||||
"referenceerror: ccountry is not defined": "ReferenceError: ccountry is not defined",
|
||||
"typeerror: invalid assignment to const 'response'": "TypeError: invalid assignment to const 'response'",
|
||||
"error: request aborted": "Error: Request aborted",
|
||||
"typeerror: can't access property \"code\", form.country is null": "TypeError: can't access property \"code\", form.country is null",
|
||||
"error: request failed with status code 504": "Error: Request failed with status code 504",
|
||||
"referenceerror: geoip is not defined": "ReferenceError: geoip is not defined",
|
||||
"referenceerror: ccodes is not defined": "ReferenceError: ccodes is not defined",
|
||||
"referenceerror: items is not defined": "ReferenceError: items is not defined",
|
||||
"referenceerror: settings is not defined": "ReferenceError: settings is not defined",
|
||||
"referenceerror: currencyhash is not defined": "ReferenceError: currencyHash is not defined",
|
||||
"typeerror: can't access property \"code\", (intermediate value).country.country is undefined": "TypeError: can't access property \"code\", (intermediate value).country.country is undefined",
|
||||
"typeerror: can't access property \"code\", (intermediate value).country[0] is undefined": "TypeError: can't access property \"code\", (intermediate value).country[0] is undefined",
|
||||
"error: request failed with status code 419": "Error: Request failed with status code 419",
|
||||
"error: network error": "Error: Network Error",
|
||||
"error: ziggy error: route 'products.count' is not in the route list.": "Error: Ziggy error: route 'products.count' is not in the route list.",
|
||||
"doc": "Doc",
|
||||
"firefox 125": "Firefox 125",
|
||||
"logs": "Logs",
|
||||
"typeerror: $matomo.trackevent is not a function": "TypeError: $matomo.trackEvent is not a function",
|
||||
"referenceerror: $matomo is not defined": "ReferenceError: $matomo is not defined",
|
||||
"server error": "Server Error"
|
||||
}
|
||||
55
package.json
@@ -5,31 +5,44 @@
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inertiajs/inertia": "^0.11.0",
|
||||
"@inertiajs/inertia": "^0.11.1",
|
||||
"@inertiajs/inertia-vue3": "^0.6.0",
|
||||
"@inertiajs/progress": "^0.2.7",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"axios": "^0.25",
|
||||
"laravel-vite-plugin": "^0.2.1",
|
||||
"lodash": "^4.17.19",
|
||||
"postcss": "^8.4.14",
|
||||
"tailwindcss": "^3.1.0",
|
||||
"vite": "^2.9.11",
|
||||
"vue": "^3.2.31"
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vitejs/plugin-vue": "^2.3.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"axios": "^0.25.0",
|
||||
"laravel-vite-plugin": "^0.2.4",
|
||||
"lodash": "^4.17.21",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^2.9.18",
|
||||
"vite-plugin-compression": "^0.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueform/multiselect": "^2.5.1",
|
||||
"flowbite": "^2.1.1",
|
||||
"flowbite-vue": "^0.1.0",
|
||||
"laravel-echo": "^1.13.0",
|
||||
"pusher-js": "^7.3.0",
|
||||
"sweetalert2": "^11.4.23",
|
||||
"@coreui/vue": "^4.10.2",
|
||||
"@inertiajs/vue3": "^1.3.0",
|
||||
"@vueform/multiselect": "^2.6.11",
|
||||
"flag-icons": "^7.5.0",
|
||||
"flowbite": "^2.5.2",
|
||||
"flowbite-vue": "git+https://git.bh.ttx.sk/jaro/flowbite-vue",
|
||||
"laravel-echo": "^1.19.0",
|
||||
"pusher-js": "^7.6.0",
|
||||
"sweetalert2": "^11.22.3",
|
||||
"vee-validate": "^4.15.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-google-charts": "^1.1.0",
|
||||
"vue-multiselect": "^3.0.0-beta.3",
|
||||
"vue-json-pretty": "^2.5.0",
|
||||
"vue-matomo": "^4.2.0",
|
||||
"vue-multiselect": "^3.3.1",
|
||||
"vue3-easy-data-table": "^1.5.47",
|
||||
"vuedraggable": "^4.1.0"
|
||||
}
|
||||
"vue3-multiselect-checkboxed": "^0.0.9",
|
||||
"vue3-popper": "^1.5.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"yup": "^1.7.0"
|
||||
},
|
||||
"bundleDependencies": [
|
||||
"flowbite-vue"
|
||||
]
|
||||
}
|
||||
|
||||
2461
pnpm-lock.yaml
generated
Normal file
BIN
public/.DS_Store
vendored
Normal file
BIN
public/images/favicon.png
Normal file
|
After Width: | Height: | Size: 940 B |
BIN
public/images/pic_online.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
public/images/pic_settings.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
23
public/sitemap.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
|
||||
<url>
|
||||
<loc>https://prices.soson.eu/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://prices.soson.eu/about/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://prices.soson.eu/exchange/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://prices.soson.eu/doc/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
BIN
resources/.DS_Store
vendored
Normal file
0
resources/adminer/plugins/.gitignore
vendored
Normal file
BIN
resources/js/.DS_Store
vendored
Normal file
56
resources/js/Components/DropDown/MobileFriendly.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<button class="z-10 relative flex items-center focus:outline-none select-none" @click="open = !open">
|
||||
<slot name="button"></slot>
|
||||
</button>
|
||||
|
||||
<!-- to close when clicked on space around it in desktop-->
|
||||
<button class="fixed inset-0 h-full w-full cursor-default focus:outline-none" v-if="open" @click="open = false" tabindex="-1"></button>
|
||||
<!--dropdown content: desktop-->
|
||||
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
|
||||
<div class="hidden md:block absolute shadow-lg border w-48 rounded py-1 px-2 text-sm mt-4 bg-white" :class="placement === 'right' ? 'right-0' : 'left-0'" v-if="open">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!--dropdown content: mobile-->
|
||||
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
|
||||
<div class="md:hidden fixed inset-x-0 bottom-0 bg-white w-full z-20 px-2 py-2 shadow-2xl leading-loose" v-if="open">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- to close when clicked on space around it in mobile-->
|
||||
<div class="md:hidden fixed w-full h-full inset-0 bg-gray-900 opacity-50 z-10" @click="open = false" v-if="open"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
placement: {
|
||||
type: String,
|
||||
default: "right",
|
||||
validator: (value) => ["right", "left"].indexOf(value) !== -1,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const onEscape = (e) => {
|
||||
if (e.key === "Esc" || e.key === "Escape") {
|
||||
this.open = false;
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", onEscape);
|
||||
|
||||
// this.$once("hook:beforeDestroy", () => {
|
||||
// document.removeEventListener("keydown", onEscape);
|
||||
// });
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,68 @@
|
||||
<script setup>
|
||||
import Swal from 'sweetalert2';
|
||||
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||
import { FwbDropdown, FwbListGroup, FwbListGroupItem } from 'flowbite-vue';
|
||||
import LogoIkea from '@/assets/Ikea_logo.svg';
|
||||
import { ref, onMounted, computed ,watch } from 'vue';
|
||||
import LogoIkea from '@/assets/price-tag.svg';
|
||||
import {
|
||||
FwbSelect,
|
||||
FwbNavbar,
|
||||
FwbNavbarCollapse,
|
||||
FwbNavbarLink,
|
||||
FwbNavbarLogo,
|
||||
FwbButton,
|
||||
FwbDropdown,
|
||||
FwbListGroup,
|
||||
FwbListGroupItem,
|
||||
FwbMegaMenu,
|
||||
FwbMegaMenuDropdown,
|
||||
FwbRadio,
|
||||
FwbFooter,
|
||||
FwbFooterCopyright,
|
||||
FwbFooterLink,
|
||||
FwbFooterLinkGroup,
|
||||
} from 'flowbite-vue';
|
||||
|
||||
import { Link, usePage } from '@inertiajs/vue3'
|
||||
|
||||
const isAuth = computed(() => page.props.auth.user)
|
||||
|
||||
import Multiselect from "vue-multiselect";
|
||||
import { settingsStore } from '../settingsStore.js';
|
||||
const menu = ref([])
|
||||
const geoip = ref(null)
|
||||
const ccodes = ref([])
|
||||
const rates = ref([])
|
||||
const currency = ref([])
|
||||
const countryCurrency = ref([])
|
||||
const currencyHash = ref([])
|
||||
|
||||
const ccountry = ref([])
|
||||
|
||||
|
||||
const ccountry_list = computed(() => {
|
||||
let computed_list = settingsStore.ccountry_list.filter((c) => (c.offline == (settingsStore.online ? 'N' : 'Y')) || settingsStore.online == true);
|
||||
console.log('COMPUTED_LIST',computed_list);
|
||||
return computed_list;
|
||||
})
|
||||
|
||||
const scountry_list = computed(() => {
|
||||
let computed_list = settingsStore.ccountry_list.filter((c) => (c.offline == (settingsStore.online ? 'N' : 'Y')) || settingsStore.online == true);
|
||||
console.log('COMPUTED_LIST',computed_list);
|
||||
return computed_list;
|
||||
})
|
||||
|
||||
let tsettings = computed(() => {
|
||||
let country = "GB";
|
||||
console.log('AAAAA',settingsStore.country);
|
||||
if (settingsStore.country != "") country = settingsStore.country.code;
|
||||
|
||||
if (settingsStore.currency != "EUR")
|
||||
settingsStore.currencyCoef = currencyHash.value[settingsStore.currency];
|
||||
else settingsStore.currencyCoef = 1.0;
|
||||
|
||||
return `${country} / ${settingsStore.currency}`;
|
||||
});
|
||||
|
||||
|
||||
const fetch_menu = async () => {
|
||||
try {
|
||||
const response = await axios.get(route('menu.user'))
|
||||
@@ -29,32 +80,231 @@ const fetch_menu = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetch_ccodes = async () => {
|
||||
try {
|
||||
const response2 = await axios.get(route("geo.ip.get"));
|
||||
geoip.value = response2.data;
|
||||
|
||||
const response = await axios.get(route("ccountry.codes"));
|
||||
ccodes.value = response.data;
|
||||
ccodes.value = Object.entries(ccodes.value)
|
||||
.map(([k, v]) => {
|
||||
if (v !== null) return { country: k, code: v };
|
||||
})
|
||||
.filter((n) => n);
|
||||
console.log("ccodes=o", ccodes.value);
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
};
|
||||
|
||||
const fetch_rates = async () => {
|
||||
try {
|
||||
const response = await axios.get(route("rates.index"));
|
||||
rates.value = response.data;
|
||||
currency.value = rates.value.map((currency) => currency.currency);
|
||||
countryCurrency.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.country_name, currency.currency])
|
||||
);
|
||||
currencyHash.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.currency, currency.rate])
|
||||
);
|
||||
|
||||
currency.value.unshift("EUR");
|
||||
console.log("RATE=", rates.value);
|
||||
console.log("HASH=", currencyHash.value);
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const fetch = async () => {
|
||||
try {
|
||||
const response = await axios.get(route("ccountry.active"));
|
||||
ccountry.value = response.data;
|
||||
let aCntry = ccountry.value.map((country) => [country.country_name]);
|
||||
settingsStore.ccountry_filter.push(...aCntry);
|
||||
|
||||
console.log('CCOUNTRY=',ccountry);
|
||||
settingsStore.ccountry_list = ccountry.value.map((country) => {
|
||||
return {
|
||||
name: country.country_name,
|
||||
code: country.country_code,
|
||||
status: country.status,
|
||||
offline: country.only_offline,
|
||||
};
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
};
|
||||
|
||||
function selectCurrency(item, id) {
|
||||
if (typeof countryCurrency.value[settingsStore.country.name] !== 'undefined') {
|
||||
settingsStore.currency = countryCurrency.value[settingsStore.country.name];
|
||||
} else {
|
||||
settingsStore.currency = "EUR";
|
||||
}
|
||||
|
||||
if (settingsStore.currency != "EUR") {
|
||||
settingsStore.currencyCoef = currencyHash.value[settingsStore.currency];
|
||||
}
|
||||
else settingsStore.currencyCoef = 1.0;
|
||||
}
|
||||
|
||||
function selectCountry(item, id) {
|
||||
let cCntry = [["Country"]];
|
||||
|
||||
let aCntry = settingsStore.countries.map((country) => [country.name]);
|
||||
if (settingsStore.countries.length == 0) aCntry = ccountry.value.map((country) => [country.country_name]);
|
||||
|
||||
settingsStore.ccountry_filter = cCntry;
|
||||
settingsStore.ccountry_filter.push(...aCntry);
|
||||
}
|
||||
|
||||
watch(ccodes, (codes) => {
|
||||
console.log("NNN=", codes);
|
||||
console.log("IP=", geoip.value);
|
||||
|
||||
let found = false;
|
||||
settingsStore.ccountry_list.forEach((c) => {
|
||||
if (c.code == geoip.value.iso_code) {
|
||||
settingsStore.country = c;
|
||||
found = true;
|
||||
}
|
||||
});
|
||||
if (found == false) settingsStore.country = settingsStore.ccountry_list.filter((c) => {return c.code == 'GB'})[0];
|
||||
if (typeof countryCurrency.value[settingsStore.country.name] !== 'undefined') {
|
||||
settingsStore.currency = countryCurrency.value[settingsStore.country.name];
|
||||
} else {
|
||||
settingsStore.currency = "EUR";
|
||||
}
|
||||
|
||||
console.log("FC=", settingsStore.country,settingsStore.currency);
|
||||
});
|
||||
|
||||
onMounted(fetch);
|
||||
onMounted(fetch_rates);
|
||||
onMounted(fetch_ccodes);
|
||||
onMounted(fetch_menu);
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
@import "flag-icons/css/flag-icons.min.css";
|
||||
</style>
|
||||
<template>
|
||||
<fwb-navbar>
|
||||
<fwb-navbar class="px-0">
|
||||
<template #logo>
|
||||
<fwb-navbar-logo alt="IKEA Price Craweler" :image-url="LogoIkea" :link="route('root')">
|
||||
IKEA Price Crawler
|
||||
<fwb-navbar-logo alt="Logo" :image-url="LogoIkea" :link="route('root')">
|
||||
<h1>IKEA price comparison</h1>
|
||||
</fwb-navbar-logo>
|
||||
</template>
|
||||
|
||||
<template #default="{ isShowMenu }">
|
||||
<fwb-navbar-collapse :is-show-menu="isShowMenu" v-if="menu.length > 0">
|
||||
|
||||
<fwb-navbar-link :link="route(menu[0].route_or_url)">
|
||||
Home
|
||||
</fwb-navbar-link>
|
||||
<fwb-navbar-link v-if="$page.props.user" :link="route('profile.show')">
|
||||
Profile
|
||||
</fwb-navbar-link>
|
||||
<fwb-navbar-link v-if="!$page.props.user" :link="route('login')">
|
||||
Login
|
||||
</fwb-navbar-link>
|
||||
|
||||
<fwb-mega-menu link="#">
|
||||
<template #default>{{ tsettings }}</template>
|
||||
</fwb-mega-menu>
|
||||
<template v-for="item in menu[0].childs" v-if="menu.length > 0">
|
||||
<fwb-navbar-link :link="route(item.route_or_url)">
|
||||
{{ item.name }}
|
||||
</fwb-navbar-link>
|
||||
</template>
|
||||
<fwb-navbar-link link="#">
|
||||
<form action="https://www.paypal.com/donate" method="post" target="_top">
|
||||
<input type="hidden" name="hosted_button_id" value="3NF26KAZBP7XA" />
|
||||
<button type="sumbit">Donate</button>
|
||||
</form>
|
||||
</fwb-navbar-link>
|
||||
</fwb-navbar-collapse>
|
||||
|
||||
</template>
|
||||
<template #mega-menu-dropdown>
|
||||
<fwb-mega-menu-dropdown>
|
||||
|
||||
<div class="flex-auto">
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Country to search in</span>
|
||||
</div>
|
||||
<div class="">
|
||||
<multiselect @select="selectCurrency" placeholder="All" :close-on-select="true" :multiple="false" v-model="settingsStore.country" :options="scountry_list" label="name" track-by="code">
|
||||
<template #option="slotProps">
|
||||
<span :class="'pl-6 fi fi-' + slotProps.option.code.toLowerCase()">{{ slotProps.option.name }}</span>
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Countries to compare with</span>
|
||||
</div>
|
||||
<div>
|
||||
<multiselect placeholder="All" @remove="selectCountry" @select="selectCountry" :close-on-select="false" :multiple="true" v-model="settingsStore.countries" :options="ccountry_list" label="name" track-by="name">
|
||||
<template #option="slotProps">
|
||||
<span :class="'pl-6 fi fi-' + slotProps.option.code.toLowerCase()">{{ slotProps.option.name }}</span>
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-auto">
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Currency recalculation</span>
|
||||
</div>
|
||||
<fwb-radio v-for="c in currency" v-model="settingsStore.currency" name="radio" :label="c" :value="c" />
|
||||
</div>
|
||||
|
||||
</fwb-mega-menu-dropdown>
|
||||
</template>
|
||||
</fwb-navbar>
|
||||
<div class="m-3">
|
||||
<div class="m-3 mb-0 pb-24">
|
||||
<slot />
|
||||
</div>
|
||||
<fwb-footer class="p-4 text-gray-400 dark:text-gray-400 rounded-none bg-white shadow md:flex md:items-center md:justify-between md:p-6 dark:bg-gray-900 bottom-0 absolute w-full">
|
||||
All information without guarantee
|
||||
<fwb-footer-copyright
|
||||
by="JD & MH Slovakia™"
|
||||
href="/"
|
||||
copyright-message=""
|
||||
/>
|
||||
</fwb-footer>
|
||||
</template>
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
_o
|
||||
|
||||
212
resources/js/Layouts/MegaMenu.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<nav class="bg-white border-gray-200 dark:border-gray-600 dark:bg-gray-900">
|
||||
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl p-4">
|
||||
<a
|
||||
href="https://flowbite.com"
|
||||
class="flex items-center space-x-3 rtl:space-x-reverse"
|
||||
>
|
||||
<img
|
||||
src="https://flowbite.com/docs/images/logo.svg"
|
||||
class="h-8"
|
||||
alt="Flowbite Logo"
|
||||
/>
|
||||
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
|
||||
>Flowbite</span
|
||||
>
|
||||
</a>
|
||||
<button
|
||||
data-collapse-toggle="mega-menu-full-cta"
|
||||
type="button"
|
||||
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
|
||||
aria-controls="mega-menu-full-cta"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg
|
||||
class="w-5 h-5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 17 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
id="mega-menu-full-cta"
|
||||
class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1"
|
||||
>
|
||||
<ul
|
||||
class="flex flex-col mt-4 font-medium md:flex-row md:mt-0 md:space-x-8 rtl:space-x-reverse"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
aria-current="page"
|
||||
>Home</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
id="mega-menu-full-cta-dropdown-button"
|
||||
data-collapse-toggle="mega-menu-full-cta-dropdown"
|
||||
data-dropdown-placement="bottom"
|
||||
class="flex items-center justify-between w-full py-2 px-3 font-medium text-gray-900 border-b border-gray-100 md:w-auto hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-600 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>
|
||||
Company
|
||||
<svg
|
||||
class="w-2.5 h-2.5 ms-3"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 4 4 4-4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>Marketplace</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>Resources</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
|
||||
>Contact</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="mega-menu-full-cta-dropdown"
|
||||
class="mt-1 bg-white border-gray-200 shadow-sm border-y dark:bg-gray-800 dark:border-gray-600"
|
||||
>
|
||||
<div
|
||||
class="grid max-w-screen-xl px-4 py-5 mx-auto text-sm text-gray-500 dark:text-gray-400 md:grid-cols-3 md:px-6"
|
||||
>
|
||||
<ul class="space-y-4 sm:mb-4 md:mb-0" aria-labelledby="mega-menu-full-cta-button">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Online Stores
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Segmentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Marketing CRM
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Online Stores
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="hidden mb-4 space-y-4 md:mb-0 sm:block">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Our Blog
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Terms & Conditions
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
License
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
|
||||
>
|
||||
Resources
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="mt-4 md:mt-0">
|
||||
<h2 class="mb-2 font-semibold text-gray-900 dark:text-white">Our brands</h2>
|
||||
<p class="mb-2 text-gray-500 dark:text-gray-400">
|
||||
At Flowbite, we have a portfolio of brands that cater to a variety of
|
||||
preferences.
|
||||
</p>
|
||||
<a
|
||||
href="#"
|
||||
class="inline-flex items-center text-sm font-medium text-blue-600 hover:underline hover:text-blue-600 dark:text-blue-500 dark:hover:text-blue-700"
|
||||
>
|
||||
Explore our brands
|
||||
<span class="sr-only">Explore our brands </span>
|
||||
<svg
|
||||
class="w-3 h-3 ms-2 rtl:rotate-180"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 14 10"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 5h12m0 0L9 1m4 4L9 9"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
20
resources/js/Layouts/UserOptions.vue
Normal file
@@ -1,34 +1,133 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||
|
||||
import GuestLayout from '../Layouts/GuestLayout.vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import axios from 'axios';
|
||||
import { Form, Field, ErrorMessage } from 'vee-validate';
|
||||
import * as yup from 'yup';
|
||||
import { ref } from "vue";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
const result = ref({ });
|
||||
const siteKey = ref('6Ld2MF0pAAAAAFpJOfU1FAxr7QiEoq1RJT1Pn2Hp');
|
||||
const schema = yup.object().shape({
|
||||
name: yup.string().required('Name is required'),
|
||||
email: yup.string().email('Email is invalid').required('Email is required'),
|
||||
subject: yup.string().required('Subject is required'),
|
||||
message: yup.string().required('Message is required'),
|
||||
// recaptcha: yup.string().required('Please verify you are human'),
|
||||
});
|
||||
|
||||
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
console.log("DATA=", data);
|
||||
try {
|
||||
|
||||
const response = await axios.post(route("ajax.send.email"), {
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
subject: data.subject,
|
||||
message: data.message,
|
||||
});
|
||||
|
||||
console.log("TEST=", response.data);
|
||||
result.value = response.data;
|
||||
} catch (e) {
|
||||
console.log('TEST=',`${e}`);
|
||||
result.value = { 'error': `${e}`};
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<GuestLayout>
|
||||
<div class="flex items-center justify-center h-screen">
|
||||
|
||||
<div class="bg-indigo-800 text-white font-bold rounded-lg border shadow-lg p-10">
|
||||
<h1>What is Ikea price comparison?</h1>
|
||||
<GuestLayout>
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="grid sm:grid-cols-2 items-center gap-16 p-8 mx-auto max-w-4xl bg-white shadow-[0_2px_10px_-3px_rgba(6,81,237,0.3)] rounded-md text-[#333] font-[sans-serif]">
|
||||
<strong>What is Ikea price comparison?</strong>
|
||||
<p>
|
||||
It is a community project of a group of enthusiasts from Slovakia.
|
||||
This service provides price comparison of Ikea products in European countries.
|
||||
Product prices are converted to EUR at the current rate and appear in resulting table.
|
||||
Information about the prices of Ikea products is obtained online.
|
||||
You can go to the appropriate product page by clicking on the country name.
|
||||
Thanks to this service, you can find out in which country a given Ikea product is the cheapest (you can sort
|
||||
them in the resulting table).
|
||||
This service provides price comparison of Ikea products in European countries.
|
||||
</p>
|
||||
<p>
|
||||
Product prices are converted to appropriate currency (you can change it in settings menu) at the current rate and appear in resulting table.
|
||||
You can go to the appropriate product page by clicking on the country name.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Thanks to this service, you can find out in which country a given Ikea product is the cheapest (you can sort them in the resulting table).
|
||||
</p>
|
||||
<br />
|
||||
Enjoy :)
|
||||
</div>
|
||||
</div>
|
||||
</GuestLayout>
|
||||
<div class="my-6">
|
||||
<div class="grid sm:grid-cols-2 items-center gap-16 p-8 mx-auto max-w-4xl bg-white shadow-[0_2px_10px_-3px_rgba(6,81,237,0.3)] rounded-md text-[#333] font-[sans-serif]">
|
||||
<div>
|
||||
<h1 class="text-3xl font-extrabold">Message</h1>
|
||||
<p class="text-sm text-gray-400 mt-3">If you have some comments or ideas for improvement about project, let as know...</p>
|
||||
|
||||
<!-- <div class="mt-12">
|
||||
<h2 class="text-lg font-extrabold">Email</h2>
|
||||
<ul class="mt-3">
|
||||
<li class="flex items-center">
|
||||
<div class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 479.058 479.058">
|
||||
<path d="M434.146 59.882H44.912C20.146 59.882 0 80.028 0 104.794v269.47c0 24.766 20.146 44.912 44.912 44.912h389.234c24.766 0 44.912-20.146 44.912-44.912v-269.47c0-24.766-20.146-44.912-44.912-44.912zm0 29.941c2.034 0 3.969.422 5.738 1.159L239.529 264.631 39.173 90.982a14.902 14.902 0 0 1 5.738-1.159zm0 299.411H44.912c-8.26 0-14.971-6.71-14.971-14.971V122.615l199.778 173.141c2.822 2.441 6.316 3.655 9.81 3.655s6.988-1.213 9.81-3.655l199.778-173.141v251.649c-.001 8.26-6.711 14.97-14.971 14.97z" data-original="#000000" />
|
||||
</svg>
|
||||
</div>
|
||||
<a target="blank" href="#" class="text-[#007bff] text-sm ml-3">
|
||||
<small class="block">Mail</small>
|
||||
<strong>https://gmail.com</strong>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-12">
|
||||
<h2 class="text-lg font-extrabold">Socials</h2>
|
||||
<ul class="flex mt-3 space-x-4">
|
||||
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
|
||||
<a href="javascript:void(0)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 24 24">
|
||||
<path d="M6.812 13.937H9.33v9.312c0 .414.335.75.75.75l4.007.001a.75.75 0 0 0 .75-.75v-9.312h2.387a.75.75 0 0 0 .744-.657l.498-4a.75.75 0 0 0-.744-.843h-2.885c.113-2.471-.435-3.202 1.172-3.202 1.088-.13 2.804.421 2.804-.75V.909a.75.75 0 0 0-.648-.743A26.926 26.926 0 0 0 15.071 0c-7.01 0-5.567 7.772-5.74 8.437H6.812a.75.75 0 0 0-.75.75v4c0 .414.336.75.75.75zm.75-3.999h2.518a.75.75 0 0 0 .75-.75V6.037c0-2.883 1.545-4.536 4.24-4.536.878 0 1.686.043 2.242.087v2.149c-.402.205-3.976-.884-3.976 2.697v2.755c0 .414.336.75.75.75h2.786l-.312 2.5h-2.474a.75.75 0 0 0-.75.75V22.5h-2.505v-9.312a.75.75 0 0 0-.75-.75H7.562z" data-original="#000000" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
|
||||
<a href="javascript:void(0)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 511 512">
|
||||
<path d="M111.898 160.664H15.5c-8.285 0-15 6.719-15 15V497c0 8.285 6.715 15 15 15h96.398c8.286 0 15-6.715 15-15V175.664c0-8.281-6.714-15-15-15zM96.898 482H30.5V190.664h66.398zM63.703 0C28.852 0 .5 28.352.5 63.195c0 34.852 28.352 63.2 63.203 63.2 34.848 0 63.195-28.352 63.195-63.2C126.898 28.352 98.551 0 63.703 0zm0 96.395c-18.308 0-33.203-14.891-33.203-33.2C30.5 44.891 45.395 30 63.703 30c18.305 0 33.195 14.89 33.195 33.195 0 18.309-14.89 33.2-33.195 33.2zm289.207 62.148c-22.8 0-45.273 5.496-65.398 15.777-.684-7.652-7.11-13.656-14.942-13.656h-96.406c-8.281 0-15 6.719-15 15V497c0 8.285 6.719 15 15 15h96.406c8.285 0 15-6.715 15-15V320.266c0-22.735 18.5-41.23 41.235-41.23 22.734 0 41.226 18.495 41.226 41.23V497c0 8.285 6.719 15 15 15h96.403c8.285 0 15-6.715 15-15V302.066c0-79.14-64.383-143.523-143.524-143.523zM466.434 482h-66.399V320.266c0-39.278-31.953-71.23-71.226-71.23-39.282 0-71.239 31.952-71.239 71.23V482h-66.402V190.664h66.402v11.082c0 5.77 3.309 11.027 8.512 13.524a15.01 15.01 0 0 0 15.875-1.82c20.313-16.294 44.852-24.907 70.953-24.907 62.598 0 113.524 50.926 113.524 113.523zm0 0" data-original="#000000" />
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
|
||||
<a href="javascript:void(0)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 24 24">
|
||||
<path d="M12 9.3a2.7 2.7 0 1 0 0 5.4 2.7 2.7 0 0 0 0-5.4Zm0-1.8a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm5.85-.225a1.125 1.125 0 1 1-2.25 0 1.125 1.125 0 0 1 2.25 0ZM12 4.8c-2.227 0-2.59.006-3.626.052-.706.034-1.18.128-1.618.299a2.59 2.59 0 0 0-.972.633 2.601 2.601 0 0 0-.634.972c-.17.44-.265.913-.298 1.618C4.805 9.367 4.8 9.714 4.8 12c0 2.227.006 2.59.052 3.626.034.705.128 1.18.298 1.617.153.392.333.674.632.972.303.303.585.484.972.633.445.172.918.267 1.62.3.993.047 1.34.052 3.626.052 2.227 0 2.59-.006 3.626-.052.704-.034 1.178-.128 1.617-.298.39-.152.674-.333.972-.632.304-.303.485-.585.634-.972.171-.444.266-.918.299-1.62.047-.993.052-1.34.052-3.626 0-2.227-.006-2.59-.052-3.626-.034-.704-.128-1.18-.299-1.618a2.619 2.619 0 0 0-.633-.972 2.595 2.595 0 0 0-.972-.634c-.44-.17-.914-.265-1.618-.298-.993-.047-1.34-.052-3.626-.052ZM12 3c2.445 0 2.75.009 3.71.054.958.045 1.61.195 2.185.419A4.388 4.388 0 0 1 19.49 4.51c.457.45.812.994 1.038 1.595.222.573.373 1.227.418 2.185.042.96.054 1.265.054 3.71 0 2.445-.009 2.75-.054 3.71-.045.958-.196 1.61-.419 2.185a4.395 4.395 0 0 1-1.037 1.595 4.44 4.44 0 0 1-1.595 1.038c-.573.222-1.227.373-2.185.418-.96.042-1.265.054-3.71.054-2.445 0-2.75-.009-3.71-.054-.958-.045-1.61-.196-2.185-.419A4.402 4.402 0 0 1 4.51 19.49a4.414 4.414 0 0 1-1.037-1.595c-.224-.573-.374-1.227-.419-2.185C3.012 14.75 3 14.445 3 12c0-2.445.009-2.75.054-3.71s.195-1.61.419-2.185A4.392 4.392 0 0 1 4.51 4.51c.45-.458.994-.812 1.595-1.037.574-.224 1.226-.374 2.185-.419C9.25 3.012 9.555 3 12 3Z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<Form @submit="onSubmit" :validation-schema="schema" v-slot="{ errors }" >
|
||||
<Field type='text' placeholder='Name' name="name" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
|
||||
<div class="bg-red-500">{{ errors.name }}</div>
|
||||
<Field type='email' placeholder='Email' name="email" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
|
||||
<div class="bg-red-500">{{ errors.email }}</div>
|
||||
<Field type='text' placeholder='Subject' name="subject" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
|
||||
<div class="bg-red-500">{{ errors.subject }}</div>
|
||||
<Field type='textarea' as='textarea' placeholder='Message' rows="6" name="message" class="w-full rounded-md px-4 border text-sm pt-2.5 outline-[#007bff]" />
|
||||
<div class="bg-red-500">{{ errors.message }}</div>
|
||||
<button type='submit' class="text-white bg-[#007bff] hover:bg-blue-600 font-semibold rounded-md text-sm px-4 py-2.5 w-full">Send</button>
|
||||
|
||||
<div v-if="errors.recaptcha" class="text-red"> {{ errors.recaptcha }} </div>
|
||||
<div v-if="result['success']" class="bg-green-500 w-full text-center mt-1">{{ result['success'] }}</div>
|
||||
<div v-if="result['error']" class="bg-red-500 w-full text-center mt-1">{{ result['error'] }}</div>
|
||||
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</GuestLayout>
|
||||
</template>
|
||||
<style>
|
||||
@import 'vue3-easy-data-table/dist/style.css';
|
||||
</style>
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
|
||||
39
resources/js/Pages/IkeaDoc.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
import GuestLayout from '../Layouts/GuestLayout.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<GuestLayout>
|
||||
<h1 class="font-extrabold">How to use the service</h1>
|
||||
<hr class="h-1 mx-auto bg-gray-100 border-0 mb-5 rounded dark:bg-gray-700">
|
||||
|
||||
<h2 class="font-bold mb-4">Main menu</h2>
|
||||
|
||||
<ol class="ml-10 list-decimal">
|
||||
<li>Type in the keyword(s) to find a product for which you want to compare the price (product name, description, article number, ...)</li>
|
||||
<li>You can either sort/filter the results or simply click on any table row to find the product prices</li>
|
||||
<li>Price comparison will appear in the <span>"Results"</span> table</li>
|
||||
</ol>
|
||||
<br/>
|
||||
To get the lowest price you can sort the results by price column.
|
||||
Click on the country name to display the corresponding product page.
|
||||
<br/>
|
||||
Some countries are not included for the comparison by default (e.g. Bulgaria, Cyprus, Greece) due to technical limitations on their websites.
|
||||
<br/>
|
||||
You can search for a product by its article number, but without the dots. For example: 40277992
|
||||
<br/>
|
||||
<br/>
|
||||
<img alt="Online" src="/images/pic_online.png" />
|
||||
<br/>
|
||||
<h2 class="font-bold mb-4">Settings menu</h2>
|
||||
|
||||
<p class="mb-2 underline">In the settings menu you can change:</p>
|
||||
<ul class="ml-10 list-disc mb-10">
|
||||
<li>the country in which to search for the product(s) to compare (default country is based on your location)</li>
|
||||
<li>list of countries where the product(s) are available for comparison (default all)</li>
|
||||
<li>currency re-calculation</li>
|
||||
</ul>
|
||||
|
||||
<img alt="Settings" src="/images/pic_settings.png" />
|
||||
</GuestLayout>
|
||||
</template>
|
||||
@@ -2,15 +2,76 @@
|
||||
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||
import GuestLayout from '../Layouts/GuestLayout.vue';
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
|
||||
import EasyTable from "vue3-easy-data-table";
|
||||
import axios from 'axios';
|
||||
|
||||
const headers = ref([
|
||||
{ text: "Id", value: "id", sortable: true },
|
||||
{ text: "Country", value: "country", sortable: true },
|
||||
{ text: "Currency", value: "currency", sortable: true },
|
||||
{ text: "Rate", value: "rate", sortable: true },
|
||||
]);
|
||||
const currencyCountryRate = ref([]);
|
||||
const rates = ref([]);
|
||||
const currencyHash = ref({});
|
||||
const currency = ref([]);
|
||||
const countryCurrency = ref([]);
|
||||
const ccountry = ref([]);
|
||||
|
||||
const fetch_rates = async () => {
|
||||
try {
|
||||
var response = await axios.get(route('rates.index'));
|
||||
rates.value = response.data;
|
||||
response = await axios.get(route('ccountry.active'))
|
||||
ccountry.value = response.data
|
||||
|
||||
currency.value = rates.value.map((currency) => currency.currency);
|
||||
countryCurrency.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.country_name, currency.currency])
|
||||
);
|
||||
currencyHash.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.currency, currency.rate]),
|
||||
);
|
||||
var i = 1;
|
||||
currencyCountryRate.value = ccountry.value.map((country) => {
|
||||
console.log(country);
|
||||
return { "country": country.country_name, "currency": country.currency_code, "rate": currencyHash.value[country.currency_code], "id": i++ }
|
||||
});
|
||||
currency.value.unshift('EUR');
|
||||
console.log("RATE=", rates.value);
|
||||
console.log('HASH=', currencyHash.value);
|
||||
console.log('CCC=',currencyCountryRate);
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __('are you want to try again') + '?',
|
||||
text: __(`${e}`),
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
})
|
||||
|
||||
response.isConfirmed && fetch()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetch_rates);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<GuestLayout>
|
||||
<div class="flex flex-wrap gap-2 justify-center align-middle ">
|
||||
<h1>Exchange</h1>
|
||||
<div class="text-center grid-flow-col max-w-fit justify-items-center">
|
||||
<div class="font-extrabold">Exchange rates and coutry list</div>
|
||||
<div class="mt-5">
|
||||
<EasyTable :rows-per-page=30 :headers="headers" :items="currencyCountryRate" alternating :hide-footer="true"></EasyTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</GuestLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import 'vue3-easy-data-table/dist/style.css';
|
||||
</style>
|
||||
|
||||
@@ -1,293 +1,662 @@
|
||||
<script setup>
|
||||
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||
import Swal from 'sweetalert2';
|
||||
import {
|
||||
getCurrentInstance,
|
||||
ref,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
computed,
|
||||
nextTick,
|
||||
watch,
|
||||
inject,
|
||||
} from "vue";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import { GChart } from 'vue-google-charts';
|
||||
import Multiselect from "vue-multiselect";
|
||||
import { GChart } from "vue-google-charts";
|
||||
|
||||
import EasyTable from "vue3-easy-data-table";
|
||||
import GuestLayout from '../Layouts/GuestLayout.vue';
|
||||
import GuestLayout from "../Layouts/GuestLayout.vue";
|
||||
|
||||
import { useForm } from '@inertiajs/inertia-vue3'
|
||||
import axios from 'axios';
|
||||
import IkeaLogo from './Ikea/IkeaLogo.vue';
|
||||
import axios from "axios";
|
||||
import filterimg from "@/assets/eglass-filter.png";
|
||||
import searchimg from "@/assets/search-icon.svg";
|
||||
import { settingsStore } from '../settingsStore.js';
|
||||
import { FwbInput } from 'flowbite-vue';
|
||||
|
||||
const props = defineProps({
|
||||
products: {
|
||||
type: Object,
|
||||
default: []
|
||||
},
|
||||
countryHash: {
|
||||
type: Object,
|
||||
default: []
|
||||
},
|
||||
menu: {
|
||||
type: Object,
|
||||
default: []
|
||||
}
|
||||
})
|
||||
const screenWidth = ref(screen.width)
|
||||
const screenHeight = ref(screen.height)
|
||||
const hover = ref(true);
|
||||
const hoverImage = ref(null);
|
||||
const selectedRow = ref(null);
|
||||
const timestamp = ref(Date.now());
|
||||
// const $matomo = inject('$matomo');
|
||||
|
||||
console.log(props.countryHash);
|
||||
let tproducts = computed(() => {
|
||||
return props.products.map((prod) => {
|
||||
prod.salesPrice = parseFloat(prod.salesPrice);
|
||||
prod.country = props.countryHash[prod.country];
|
||||
return prod;
|
||||
})
|
||||
})
|
||||
|
||||
const type = 'GeoChart';
|
||||
const form = useForm({
|
||||
countries: '',
|
||||
country: '',
|
||||
codes: '',
|
||||
currency: 'EUR',
|
||||
});
|
||||
|
||||
const ccodes = ref([]);
|
||||
const ccountry = ref([]);
|
||||
const ccountry_filter = ref([['Country'],]);
|
||||
const ccountry_list = ref(['TEST']);
|
||||
const selected = ref(null);
|
||||
const article = ref('');
|
||||
const currency = ref([]);
|
||||
const hrates = ref([
|
||||
{ text: "Currency", value: "currency", sortable: true },
|
||||
{ text: "Country", value: "country_name", sortable: true },
|
||||
{ text: "Rate", value: "rate", sortable: true }
|
||||
]);
|
||||
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },
|
||||
const hproducts = ref([
|
||||
{ text: "Country", value: "country", sortable: true },
|
||||
{ text: "Name", value: "name", sortable: true },
|
||||
{ text: "Price", value: "salesPrice", sortable: true }
|
||||
const sdropdown = ref([
|
||||
{ text: "Description", value: "typeName" },
|
||||
{ text: "Item name", value: "name" },
|
||||
{ text: "Code of product", value: "code" },
|
||||
]);
|
||||
|
||||
const products = ref([]);
|
||||
const searchField = ref('descLong');
|
||||
const searchValue = ref('');
|
||||
|
||||
const countryHash = ref([]);
|
||||
const countryCurrency = ref({});
|
||||
const showItemFilter = ref(false);
|
||||
const showDescFilter = ref(false);
|
||||
const showDescLongFilter = ref(false);
|
||||
const showUnitsFilter = ref(false);
|
||||
const geoip = ref({});
|
||||
|
||||
const rates = ref([]);
|
||||
const options_items = ref([]);
|
||||
const selected_item = ref(null);
|
||||
const itemCode = ref(null);
|
||||
const googlemapbox = ref(null);
|
||||
const googlemapchart = ref(null);
|
||||
|
||||
const gChartWidth = computed(() => screenWidth.value > 420 ? 400 : (screenWidth.value - 30 - 5));
|
||||
const gChartHeight = computed(() => gChartWidth.value == 400 ? 250 : (screenWidth.value *67/100) - 28);
|
||||
console.log('GCH',screenWidth.value, screenHeight.value, gChartWidth.value,gChartHeight.value);
|
||||
|
||||
const options = {
|
||||
region: 150,
|
||||
|
||||
width: 700,
|
||||
height: 500,
|
||||
colorAxis: {colors: ['#0000ff']},
|
||||
backgroundColor: '#81d4fa',
|
||||
datalessRegionColor: '#ffffff',
|
||||
//datalessRegionColor: '#f8bbd0',
|
||||
//defaultColor: '#f5f5f5',
|
||||
width: gChartWidth.value,
|
||||
height: gChartHeight.value,
|
||||
};
|
||||
|
||||
const chart_settings = {
|
||||
packages: ['geochart', 'map'],
|
||||
packages: ["geochart", "map"],
|
||||
mapsApiKey: "AIzaSyAJaLArHgTmQPMOSogitG-umhZilVIgdNU",
|
||||
};
|
||||
|
||||
const gchartEvents = ref({
|
||||
regionClick: () => {
|
||||
const selection = getSelection()
|
||||
const selection = getSelection();
|
||||
console.log(selection);
|
||||
console.log("T");
|
||||
},
|
||||
});
|
||||
|
||||
const onHover = (data) => {
|
||||
console.log('HOVER',data.target.src);
|
||||
hoverImage.value = data.target.src;
|
||||
hover.value = !hover.value;
|
||||
};
|
||||
|
||||
const chart_coutries = computed(() => {
|
||||
let countries = [["Country", "Count"]];
|
||||
|
||||
for (let i=0; i < settingsStore.ccountry_list.length ; i++) {
|
||||
if (settingsStore.ccountry_list[i].offline == (settingsStore.online ? 'N' : 'Y') || settingsStore.online == true)
|
||||
countries.push([settingsStore.ccountry_list[i].name, settingsStore.products_count[settingsStore.ccountry_list[i].code] ]);
|
||||
}
|
||||
},);
|
||||
return countries;
|
||||
})
|
||||
|
||||
|
||||
const tproducts = computed(() => {
|
||||
console.log("PR=", products);
|
||||
console.log("CC=", countryCurrency.value);
|
||||
return products.value.map((p) => {
|
||||
const prod = { ...p };
|
||||
prod.currency = countryCurrency.value[prod.countryName];
|
||||
if (currencyHash.value[prod.currency])
|
||||
prod.calcPrice = prod.salesPrice / currencyHash.value[prod.currency];
|
||||
else {
|
||||
prod.calcPrice = prod.salesPrice;
|
||||
prod.currency = "EUR";
|
||||
}
|
||||
if (prod.tag == "NONE") prod.tag = "";
|
||||
prod.salesPrice = Math.round(prod.salesPrice * 100) / 100;
|
||||
prod.calcPrice =
|
||||
Math.round(prod.calcPrice * settingsStore.currencyCoef * 100) / 100;
|
||||
console.log('CHA=',currencyHash.value[prod.currency]);
|
||||
prod.rate =
|
||||
Math.round((currencyHash.value[prod.currency] == undefined? 1.0 : currencyHash.value[prod.currency] ) / (settingsStore.currency != "EUR" ? currencyHash.value[settingsStore.currency] : 1.0)*100)/100;
|
||||
if (prod.tag != null && prod.tag.length > 1) prod.tag = prod.tag.replace(/_/g, " ");
|
||||
return prod;
|
||||
});
|
||||
});
|
||||
const bodyRowClassNameFunction = (item) => {
|
||||
if (item.country == settingsStore.country.code) return "result-country";
|
||||
};
|
||||
const filterOptions = computed(() => {
|
||||
const filterOptionsArray = [];
|
||||
if (productsCriteria.value != "All" && productsCriteria.value != null) {
|
||||
filterOptionsArray.push({
|
||||
field: "item",
|
||||
comparison: "=",
|
||||
criteria: productsCriteria.value,
|
||||
});
|
||||
}
|
||||
if (descCriteria.value != "All" && descCriteria.value != null) {
|
||||
filterOptionsArray.push({
|
||||
field: "desc",
|
||||
comparison: "=",
|
||||
criteria: descCriteria.value,
|
||||
});
|
||||
}
|
||||
if (unitsCriteria.value != "All" && unitsCriteria.value != null) {
|
||||
filterOptionsArray.push({
|
||||
field: "units",
|
||||
comparison: "=",
|
||||
criteria: unitsCriteria.value,
|
||||
});
|
||||
}
|
||||
return filterOptionsArray;
|
||||
});
|
||||
|
||||
let uniqProducts = computed(() => {
|
||||
var output = ["All"];
|
||||
var keys = [];
|
||||
options_items.value.forEach((element) => {
|
||||
var key = element.item;
|
||||
if (keys.indexOf(key) === -1) {
|
||||
keys.push(key);
|
||||
output.push(element.item);
|
||||
}
|
||||
});
|
||||
console.log("OUT", output);
|
||||
return output;
|
||||
});
|
||||
|
||||
let uniqDesc = computed(() => {
|
||||
var output = ["All"];
|
||||
var keys = [];
|
||||
options_items.value.forEach((element) => {
|
||||
var key = element.desc;
|
||||
if (keys.indexOf(key) === -1) {
|
||||
keys.push(key);
|
||||
output.push(element.desc);
|
||||
}
|
||||
});
|
||||
console.log("OUT2", output);
|
||||
return output;
|
||||
});
|
||||
|
||||
let uniqUnits = computed(() => {
|
||||
var output = ["All"];
|
||||
var keys = [];
|
||||
options_items.value.forEach((element) => {
|
||||
var key = element.units;
|
||||
if (keys.indexOf(key) === -1) {
|
||||
keys.push(key);
|
||||
output.push(element.units);
|
||||
}
|
||||
});
|
||||
console.log("OUT3", output);
|
||||
return output;
|
||||
});
|
||||
|
||||
const productsCriteria = ref(uniqProducts.value[0]);
|
||||
const descCriteria = ref(uniqDesc.value[0]);
|
||||
const unitsCriteria = ref(uniqUnits.value[0]);
|
||||
|
||||
const type = "GeoChart";
|
||||
settingsStore.field = sdropdown.value[0];
|
||||
|
||||
const ccodes = ref([]);
|
||||
const ccountry = ref([]);
|
||||
|
||||
const ccountry_list = ref(["TEST"]);
|
||||
const currencyHash = ref({});
|
||||
const currency = ref([]);
|
||||
|
||||
const items = ref([]);
|
||||
|
||||
const hrates = ref([
|
||||
{ text: "Currency", value: "currency", sortable: true },
|
||||
{ text: "Country", value: "country_name", sortable: true },
|
||||
{ text: "Rate", value: "rate", sortable: true },
|
||||
]);
|
||||
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },
|
||||
|
||||
const tprice = computed(() => {
|
||||
return `Price / ${settingsStore.currency}`;
|
||||
})
|
||||
|
||||
const hproducts = ref([
|
||||
{ text: "Country", value: "countryName", sortable: true },
|
||||
{ text: "Tag", value: "tag", sortable: true },
|
||||
{ text: "Local Price", value: "salesPrice", sortable: true },
|
||||
{ text: "Cur", value: "currency", sortable: false },
|
||||
{ text: "Rate", value: "rate" },
|
||||
{ text: tprice, value: "calcPrice", sortable: true },
|
||||
]);
|
||||
const hresults = ref([
|
||||
{ text: "Code", value: "code", sortable: true },
|
||||
// { text: "Name of product", value: "item", sortable: true },
|
||||
// { text: "Description", value: "desc", sortable: true },
|
||||
{ text: "Description Long", value: "descLong", sortable: true },
|
||||
{ text: "Units", value: "units", sortable: true },
|
||||
{ text: "Price", value: "price", sortable: true },
|
||||
{ text: "Image", value: "img", sortable: false },
|
||||
]);
|
||||
|
||||
const sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
const fetch = async () => {
|
||||
try {
|
||||
const response = await axios.get(route('ccountry.active'))
|
||||
ccountry.value = response.data
|
||||
const response = await axios.get(route("ccountry.active"));
|
||||
ccountry.value = response.data;
|
||||
let aCntry = ccountry.value.map((country) => [country.country_name]);
|
||||
ccountry_filter.value.push(...aCntry);
|
||||
settingsStore.ccountry_filter.push(...aCntry);
|
||||
|
||||
ccountry_list.value = ccountry.value.map((country) => country.country_name);
|
||||
var i = 1;
|
||||
items.value = ccountry.value.map((country) => { return { "country": country.country_name, "currency": country.currency_code, "id": i++ } });
|
||||
console.log("TEST=", ccountry_filter.value, ccountry_list.value);
|
||||
items.value = ccountry.value.map((country) => {
|
||||
return {
|
||||
country: country.country_name,
|
||||
currency: country.currency_code,
|
||||
id: i++,
|
||||
};
|
||||
});
|
||||
console.log("TEST=", settingsStore.ccountry_filter, ccountry_list.value);
|
||||
const response2 = await axios.get(route("settings.index"));
|
||||
settingsStore.settings = response2.data;
|
||||
console.log("SETTINGS=",settingsStore.settings);
|
||||
|
||||
const response3 = await axios.get(route("products.count"));
|
||||
settingsStore.products_count = Object.fromEntries(
|
||||
response3.data.map((c) => [c.country, c.cnt])
|
||||
);
|
||||
console.log("PC=",settingsStore.products_count);
|
||||
|
||||
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __('are you want to try again') + '?',
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: 'question',
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
})
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch()
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetch_rates = async () => {
|
||||
try {
|
||||
const response = await axios.get(route('rates.index'))
|
||||
const response = await axios.get(route("rates.index"));
|
||||
rates.value = response.data;
|
||||
currency.value = rates.value.map((currency) => currency.currency);
|
||||
currency.value.unshift('EUR');
|
||||
countryCurrency.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.country_name, currency.currency])
|
||||
);
|
||||
currencyHash.value = Object.fromEntries(
|
||||
rates.value.map((currency) => [currency.currency, currency.rate])
|
||||
);
|
||||
|
||||
currency.value.unshift("EUR");
|
||||
console.log("RATE=", rates.value);
|
||||
console.log("HASH=", currencyHash.value);
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __('are you want to try again') + '?',
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: 'question',
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
})
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch()
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const fetch_ccodes = async () => {
|
||||
try {
|
||||
const response = await axios.get(route('ccountry.codes'))
|
||||
const response2 = await axios.get(route("geo.ip.get"));
|
||||
geoip.value = response2.data;
|
||||
|
||||
const response = await axios.get(route("ccountry.codes"));
|
||||
ccodes.value = response.data;
|
||||
ccodes.value = Object.entries(ccodes.value).map(([k, v]) => { if (v !== null) return { "country": k, "code": v } }).filter(n => n);
|
||||
ccodes.value = Object.entries(ccodes.value)
|
||||
.map(([k, v]) => {
|
||||
if (v !== null) return { country: k, code: v };
|
||||
})
|
||||
.filter((n) => n);
|
||||
console.log("ccodes=", ccodes.value);
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __('are you want to try again') + '?',
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: 'question',
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
})
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch()
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const async_search = async () => {
|
||||
if (settingsStore.text.length < 2) return;
|
||||
timestamp.value = Date.now();
|
||||
|
||||
const async_search = async (item) => {
|
||||
try {
|
||||
if (item.length >= 2) {
|
||||
let country = null;
|
||||
if (settingsStore.country.code !== undefined) country = settingsStore.country.code;
|
||||
|
||||
console.log('SEARCH', item, form.country.code, route('products.search', [item, form.country.code]));
|
||||
const response = await axios.get(route('products.search', [item, form.country.code]));
|
||||
console.log('SETTINGS',settingsStore.country);
|
||||
console.log('URL',route("products.search", ['all', settingsStore.text, country]));
|
||||
const response = await axios.get(route("products.search", ['all', settingsStore.text, country]));
|
||||
|
||||
options_items.value = response.data.map((i) => { return { "item": i.name, "desc": i.typeName, "img": i.mainImageUrl, "code": i.code, "url": i.url } });
|
||||
options_items.value = response.data.map((i) => {
|
||||
return {
|
||||
item: i.name,
|
||||
desc: i.typeName,
|
||||
img: i.mainImageUrl,
|
||||
code: i.code,
|
||||
url: i.url,
|
||||
price: parseFloat(i.salesPrice),
|
||||
units: i.priceUnit,
|
||||
descLong: i.mainImageAlt,
|
||||
globalCode: i.itemNoGlobal,
|
||||
};
|
||||
});
|
||||
console.log("VALUES=", options_items.value);
|
||||
}
|
||||
|
||||
productsCriteria.value = null;
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __('are you want to try again') + '?',
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: 'question',
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
})
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch()
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function customLabel({ item, desc, code }) {
|
||||
//console.log(item);
|
||||
return `${item} - ${desc} - ${code}`;
|
||||
}
|
||||
const headers = ref([
|
||||
{ text: "Id", value: "id", sortable: true },
|
||||
{ text: "Country", value: "country", sortable: true },
|
||||
{ text: "Currency", value: "currency" },
|
||||
]);
|
||||
const items = ref([]);
|
||||
|
||||
function selectCountry(item, id) {
|
||||
let cCntry = [['Country'],];
|
||||
console.log(form.countries);
|
||||
let aCntry = form.countries.map((country) => [country]);
|
||||
|
||||
ccountry_filter.value = cCntry;
|
||||
ccountry_filter.value.push(...aCntry);
|
||||
const clear_all = () => {
|
||||
settingsStore.text = '';
|
||||
products.value = [];
|
||||
options_items.value = [];
|
||||
}
|
||||
|
||||
onMounted(fetch);
|
||||
onMounted(fetch_rates);
|
||||
onMounted(fetch_ccodes);
|
||||
onMounted(() => {
|
||||
console.log('HEIGHT MAP', googlemapbox.value);
|
||||
});
|
||||
const showRow = async (item) => {
|
||||
console.log("ITEM=", item);
|
||||
|
||||
itemCode.value = item.code;
|
||||
try {
|
||||
const response = await axios.post(route("products.compare"), {
|
||||
codes: item.globalCode,
|
||||
countries: settingsStore.countries,
|
||||
currency: settingsStore.currency,
|
||||
online: settingsStore.online,
|
||||
text: settingsStore.text,
|
||||
country: settingsStore.country.code,
|
||||
});
|
||||
|
||||
//console.log('MATOMO',$matomo);
|
||||
products.value = response.data.products;
|
||||
countryHash.value = response.data.countryHash;
|
||||
console.log("TEST=", response.data);
|
||||
nextTick(() => {
|
||||
options.width=googlemapbox.value.clientWidth;
|
||||
googlemapchart.value += 1;
|
||||
})
|
||||
} catch (e) {
|
||||
const response = await Swal.fire({
|
||||
title: __("are you want to try again") + "?",
|
||||
text: __(`${e}`),
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
showCloseButton: true,
|
||||
});
|
||||
|
||||
response.isConfirmed && fetch();
|
||||
}
|
||||
};
|
||||
|
||||
const submit = () => {
|
||||
console.log('ITEM=', form);
|
||||
if (form.codes.length == 0) {
|
||||
console.log("ITEM=", form);
|
||||
if (settingsStore.codes.length == 0) {
|
||||
Swal.fire({
|
||||
title: "Empty code",
|
||||
text: "You must enter product!",
|
||||
icon: "error"
|
||||
icon: "error",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
form.post(route('products.compare'));
|
||||
settingsStore.post(route("products.compare"));
|
||||
};
|
||||
</script>
|
||||
|
||||
const selectedRowClassNameFunction = (item) => {
|
||||
if (item.code == itemCode.value) return 'selected-row';
|
||||
};
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<GuestLayout>
|
||||
<div class="flex flex-wrap gap-2 justify-center align-middle ">
|
||||
<div class="justify-center rounded-md border-black border-8 max-h-[518px]">
|
||||
<GChart :events="gchartEvents" :type="type" :data="ccountry_filter" :options="options"
|
||||
:settings="chart_settings" />
|
||||
</div>
|
||||
<div class="mr-auto">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Zadaj krajiny v ktorych chces vyhadavat</span>
|
||||
</div>
|
||||
<div>
|
||||
<multiselect :close-on-select="false" :multiple="true" v-model="form.countries" :options="ccountry_list"
|
||||
@select="selectCountry" @remove="selectCountry">
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Krajina produktu na vyhladanie</span>
|
||||
</div>
|
||||
<div>
|
||||
<multiselect :close-on-select="false" :multiple="false" v-model="form.country" :options="ccodes"
|
||||
label="country" track-by="code">
|
||||
</multiselect>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-extrabold font-mono">Zadaj polozku</span>
|
||||
</div>
|
||||
<div>
|
||||
<multiselect v-model="form.codes" label="item" track-by="item" :custom-label="customLabel"
|
||||
:internal-search="false" placeholder="Find item" :searchable="true" :options="options_items"
|
||||
@search-change="async_search" />
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-extrabold font-mono">Prepocet do meny</span>
|
||||
</div>
|
||||
<div>
|
||||
<multiselect v-model="form.currency" :multiple="false" :allow-empty="false" placeholder="Currency"
|
||||
:searchable="true" :options="currency" />
|
||||
</div>
|
||||
<div class="text-end mt-2">
|
||||
<button type="submit"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
:disabled="form.processing" :class="{ 'opacity-25': form.processing }">
|
||||
Search
|
||||
|
||||
<div class="flex flex-wrap gap-2 justify-center align-middle">
|
||||
|
||||
<div id="popup-modal" tabindex="-1" :class="'hidden'"
|
||||
class="absolute inset-y-1/3 left-1/3 sm:inset-10 sm:left-10 z-50 justify-center items-center w-full max-h-full">
|
||||
<div class="relative p-4 w-full max-w-md max-h-full">
|
||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<button @click="hover=true" type="button"
|
||||
class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
data-modal-hide="popup-modal">
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||
viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
</svg>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-5">
|
||||
<EasyTable :rows-per-page=10 :headers="headers" :items="items" alternating></EasyTable>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<EasyTable :rows-per-page=15 :headers="hrates" :items="rates" alternating></EasyTable>
|
||||
<div class="p-4 md:p-5 text-center">
|
||||
|
||||
|
||||
<img :src="hoverImage" />
|
||||
<!-- <button data-modal-hide="popup-modal" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
|
||||
Yes, I'm sure
|
||||
</button> -->
|
||||
<button @click="hover=true" data-modal-hide="popup-modal" type="button"
|
||||
class="py-2.5 px-5 mt-2 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex flex-col start-0 row-start-1">
|
||||
<div ref="googlemapbox" class="justify-center rounded-md border-black border-8 max-h-[328px]">
|
||||
<GChart :key="googlemapchart" :events="gchartEvents" :type="type" :data="chart_coutries"
|
||||
:options="options" :settings="chart_settings" :resizeDebounce="500" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col start-0">
|
||||
<!-- <span class="font-extrabold font-mono">Results <b v-if="itemCode">for {{ itemCode }}</b> -->
|
||||
<span class="font-extrabold font-mono">Prices across Europe
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<EasyTable id="results" table-class-name="results" sortBy="countryName" :rows-per-page="30"
|
||||
:headers="hproducts" :items="tproducts" :body-row-class-name="bodyRowClassNameFunction"
|
||||
:hide-rows-per-page="true" :hide-footer="true"
|
||||
sort-by="calcPrice" alternating>
|
||||
<template #item-countryName="{ countryName, url }">
|
||||
<a class="underline" target="_blank" :href="url">{{
|
||||
countryName
|
||||
}}</a>
|
||||
</template>
|
||||
</EasyTable>
|
||||
<!--
|
||||
<div v-if="settingsStore.online == true">Online</div>
|
||||
<div v-else-if="'LAST_REFRESH_TIME' in settingsStore.settings">Last Refresh Time: {{
|
||||
settingsStore.settings["LAST_REFRESH_TIME"] }}</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:w-2/5">
|
||||
<form @submit.prevent="submit">
|
||||
<div>
|
||||
<div class="flex flex-col start-0">
|
||||
<span class="font-extrabold font-mono">Vysledky vyhladavania</span>
|
||||
<div class="w-full">
|
||||
<div>
|
||||
<span class="font-extrabold font-mono">Search IKEA product:</span>
|
||||
</div>
|
||||
<div>
|
||||
<EasyTable :rows-per-page=15 :headers="hproducts" :items="tproducts" alternating>
|
||||
<template #item-name="{ name, url }">
|
||||
<a class="underline" target="_blank" :href="url">{{ name }}</a>
|
||||
<fwb-input @input="async_search" v-model="settingsStore.text" :placeholder="settingsStore.online ? 'Code of product' : 'METOD, BESTA, MALM, ...'"
|
||||
size="md">
|
||||
<template #suffix>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
|
||||
</svg>
|
||||
</template>
|
||||
</fwb-input>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!--
|
||||
<div>
|
||||
<span class="font-extrabold font-mono">Online:</span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox"
|
||||
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
v-model="settingsStore.online" @input="clear_all" />
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="font-extrabold font-mono mt-2">Click on any table row to find the product prices</div>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<input v-if="showDescLongFilter" placeholder="Search in long description"
|
||||
class="mb-1 px-2 py-1 placeholder-blueGray-300 text-blueGray-600 relative bg-white bg-white rounded text-sm border border-blueGray-300 outline-none focus:outline-none focus:ring w-full"
|
||||
type="text" v-model="searchValue">
|
||||
<EasyTable class="none" :rows-per-page="20" :headers="hresults" :items="options_items"
|
||||
:search-field="searchField" :search-value="searchValue"
|
||||
:body-row-class-name="selectedRowClassNameFunction" @click-row="showRow"
|
||||
:filter-options="filterOptions"
|
||||
alternating>
|
||||
<template #item-img="{ code, img }">
|
||||
<img v-on:mouseover="onHover" class="h-12" :src="img" :alt="code" />
|
||||
</template>
|
||||
<template #header-item="header">
|
||||
<div class="filter-column">
|
||||
<img :src="filterimg" class="filter-icon" alt="filter"
|
||||
@click.stop="showItemFilter = !showItemFilter" />
|
||||
{{ header.text }}
|
||||
<div class="filter-menu filter-sport-menu" v-if="showItemFilter">
|
||||
<multiselect v-model="productsCriteria" :options="uniqProducts"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #header-descLong="header">
|
||||
<div class="filter-column">
|
||||
<img :src="searchimg" class="filter-icon" alt="filter long description"
|
||||
@click.stop="showDescLongFilter = !showDescLongFilter" />
|
||||
{{ header.text }}
|
||||
</div>
|
||||
</template>
|
||||
<template #header-desc="header">
|
||||
<div class="filter-column">
|
||||
<img :src="filterimg" class="filter-icon" alt="filter description"
|
||||
@click.stop="showDescFilter = !showDescFilter" />
|
||||
{{ header.text }}
|
||||
<div class="filter-menu filter-sport-menu" v-if="showDescFilter">
|
||||
<multiselect v-model="descCriteria" :options="uniqDesc"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #header-units="header">
|
||||
<div class="filter-column">
|
||||
<img :src="filterimg" class="filter-icon" alt="filter units"
|
||||
@click.stop="showUnitsFilter = !showUnitsFilter" />
|
||||
{{ header.text }}
|
||||
<div class="filter-menu filter-menu-units" v-if="showUnitsFilter">
|
||||
<multiselect v-model="unitsCriteria" :options="uniqUnits"></multiselect>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</EasyTable>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
</GuestLayout>
|
||||
</template>
|
||||
<style>
|
||||
@import 'vue3-easy-data-table/dist/style.css';
|
||||
@import "vue3-easy-data-table/dist/style.css";
|
||||
|
||||
.filter-column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
width: 15px !important;
|
||||
height: 15px !important;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.filter-menu {
|
||||
padding: 5px 5px;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
width: 328px;
|
||||
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.filter-menu-units {
|
||||
padding: 5px 5px;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
width: 128px;
|
||||
}
|
||||
|
||||
.selected-row {
|
||||
--easy-table-body-row-hover-background-color: #e5e6f9;
|
||||
--easy-table-body-row-background-color: #e5e6f9;
|
||||
--easy-table-body-row-font-color: #000;
|
||||
--easy-table-body-even-row-background-color: #e5e6f9;
|
||||
}
|
||||
|
||||
:root .result-country {
|
||||
--easy-table-body-row-background-color: #e5e6f9;
|
||||
--easy-table-body-even-row-background-color: #e5e6f9;
|
||||
}
|
||||
</style>
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
|
||||
|
||||
|
||||
127
resources/js/Pages/Superuser/Activity/Logging.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup>
|
||||
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
|
||||
import Card from '@/Components/Card.vue';
|
||||
import Icon from '@/Components/Icon.vue';
|
||||
import Builder from '@/Components/DataTable/Builder.vue';
|
||||
import Th from '@/Components/DataTable/Th.vue';
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
|
||||
import {ref} from 'vue';
|
||||
const dateValue = ref([]);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DashboardLayout
|
||||
title="Logging Activity"
|
||||
>
|
||||
|
||||
<Card class="bg-white dark:bg-gray-700 dark:text-gray-200">
|
||||
<template #header>
|
||||
<div class="flex items-center space-x-2 bg-gray-200 dark:bg-gray-800 p-2">
|
||||
<p class="lowercase first-letter:capitalize font-semibold">
|
||||
Logging for Today
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<Builder
|
||||
:url="route('superuser.activity.logging')"
|
||||
>
|
||||
<template #thead="table">
|
||||
<tr class="bg-gray-200 dark:bg-gray-800 border-gray-300 dark:border-gray-900">
|
||||
<Th
|
||||
:table="table"
|
||||
:sort="false"
|
||||
name="id"
|
||||
class="border p-2 text-center"
|
||||
>
|
||||
ID
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
:table="table"
|
||||
:sort="true"
|
||||
name="level_name"
|
||||
class="border px-3 py-2 text-center whitespace-nowrap"
|
||||
>
|
||||
Level Name
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
:table="table"
|
||||
:sort="true"
|
||||
name="message"
|
||||
class="border px-3 py-2 text-center whitespace-nowrap"
|
||||
>
|
||||
Message
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
:table="table"
|
||||
:sort="true"
|
||||
name="context"
|
||||
class="border px-3 py-2 text-center whitespace-nowrap"
|
||||
>
|
||||
Context
|
||||
</Th>
|
||||
|
||||
<Th
|
||||
:table="table"
|
||||
:sort="true"
|
||||
name="logged_at"
|
||||
class="border px-3 py-2 text-center whitespace-nowrap"
|
||||
>
|
||||
Logged At
|
||||
</Th>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<template #tbody="{ data }">
|
||||
<TransitionGroup
|
||||
enterActiveClass="transition-all duration-100"
|
||||
leaveActiveClass="transition-all duration-50"
|
||||
enterFromClass="opacity-0 -scale-y-100"
|
||||
leaveToClass="opacity-0 -scale-y-100"
|
||||
>
|
||||
<tr
|
||||
v-for="(rec, i) in data"
|
||||
:key="i"
|
||||
class="dark:hover:bg-gray-600 dark:border-gray-800 transition-all duration-300"
|
||||
>
|
||||
<td class="px-2 py-1 border border-inherit text-center">
|
||||
{{ rec.id }}
|
||||
</td>
|
||||
|
||||
<td class="px-2 py-1 border border-inherit ">
|
||||
{{ rec.level_name }}
|
||||
</td>
|
||||
|
||||
<td class="px-2 py-1 border border-inherit ">
|
||||
{{ rec.message }}
|
||||
</td>
|
||||
|
||||
<td class="px-2 py-1 border border-inherit ">
|
||||
<vue-json-pretty :data="JSON.parse(rec.context)" />
|
||||
</td>
|
||||
|
||||
<td class="px-2 py-1 border border-inherit ">
|
||||
{{ rec.logged_at }}
|
||||
</td>
|
||||
</tr>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
</Builder>
|
||||
</template>
|
||||
</Card>
|
||||
</DashboardLayout>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import 'vue-json-pretty/lib/styles.css';
|
||||
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@ import Swal from 'sweetalert2';
|
||||
import { Inertia } from '@inertiajs/inertia';
|
||||
import axios from 'axios';
|
||||
import * as commons from './common.js'
|
||||
// import VueMatomo from 'vue-matomo';
|
||||
|
||||
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
|
||||
|
||||
@@ -25,18 +26,25 @@ Object.keys(commons).forEach(key => Object.defineProperty(window, key, {
|
||||
|
||||
createInertiaApp({
|
||||
title: (title) => `${title} - ${appName}`,
|
||||
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
|
||||
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue'),import.meta.glob('./assets/**') ),
|
||||
setup({ el, app, props, plugin }) {
|
||||
return createApp({ render: () => h(app, props) })
|
||||
.use(plugin)
|
||||
.use(ZiggyVue, Ziggy)
|
||||
// .use(VueMatomo, {
|
||||
// // Configure your matomo server and site by providing
|
||||
// host: 'https://matomo.soson.eu',
|
||||
// siteId: 3,
|
||||
// })
|
||||
.mixin({
|
||||
methods: {
|
||||
...commons,
|
||||
themes: () => Themes,
|
||||
},
|
||||
})
|
||||
.mount(el);
|
||||
// .provide('$matomo', VueMatomo)
|
||||
.mount(el)
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
BIN
resources/js/assets/.DS_Store
vendored
Normal file
BIN
resources/js/assets/eglass-filter.png
Normal file
|
After Width: | Height: | Size: 869 B |
BIN
resources/js/assets/favicon.png
Normal file
|
After Width: | Height: | Size: 940 B |
53
resources/js/assets/price-tag.svg
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 511.846 511.846" xml:space="preserve">
|
||||
<style>
|
||||
path {
|
||||
fill: black;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: white; }
|
||||
}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
|
||||
<path d="M443.068,312.107c-4.079-2.389-9.301-1.024-11.674,3.046l-97.818,166.835c-7.066,12.228-22.741,16.444-34.594,9.6
|
||||
l-19.055-12.348c-3.959-2.569-9.242-1.442-11.802,2.517c-2.569,3.951-1.434,9.242,2.517,11.802l19.43,12.578
|
||||
c6.699,3.874,14.029,5.709,21.265,5.709c14.754,0,29.107-7.637,36.983-21.274l97.783-166.793
|
||||
C448.486,319.718,447.13,314.487,443.068,312.107z"/>
|
||||
<path d="M468.881,0H332.348c-26.402,0-46.071,8.405-65.766,28.1c-3.336,3.337-3.336,8.73,0,12.066
|
||||
c3.337,3.337,8.73,3.337,12.066,0c16.623-16.623,31.684-23.1,53.7-23.1h136.533c14.114,0,25.6,11.486,25.6,25.6V179.2
|
||||
c0,22.025-6.477,37.077-23.108,53.7L217.95,486.682c-9.677,9.66-26.539,9.668-36.198,0L24.866,329.796
|
||||
c-4.838-4.838-7.501-11.264-7.501-18.108c0-6.835,2.662-13.261,7.492-18.091L253.909,64.87c3.524,0.896,7.057,1.792,10.718,2.773
|
||||
c34.714,9.301,62.106,18.91,82.526,27.486c-4.019,7.441-6.272,15.778-6.272,24.337c0,28.237,22.963,51.2,51.2,51.2
|
||||
c28.237,0,51.2-22.963,51.2-51.2c0-28.237-22.963-51.2-51.2-51.2c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533
|
||||
c18.825,0,34.133,15.309,34.133,34.133c0,18.825-15.309,34.133-34.133,34.133s-34.133-15.309-34.133-34.133
|
||||
c0-6.144,1.681-12.143,4.779-17.365c14.78,7.074,24.021,13.039,27.674,16.845c-1.562,0.444-3.977,0.939-7.62,1.254
|
||||
c-4.693,0.418-8.166,4.557-7.748,9.25c0.393,4.437,4.113,7.791,8.491,7.791c0.247,0,0.503-0.017,0.759-0.034
|
||||
c5.248-0.469,21.214-1.877,24.286-13.321c8.405-31.394-114.756-66.091-139.529-72.73
|
||||
c-37.897-10.155-74.291-17.101-102.494-19.558c-34.654-3.029-51.533,0.913-54.707,12.774
|
||||
c-1.519,5.692-5.572,20.804,63.616,48.085c4.378,1.732,9.344-0.418,11.068-4.804c1.724-4.386-0.427-9.344-4.813-11.068
|
||||
c-33.425-13.184-47.275-22.844-51.712-27.392c10.522-3.089,46.507-2.193,104.457,10.982L12.8,281.523
|
||||
c-8.064,8.064-12.501,18.773-12.501,30.165c0,11.401,4.437,22.118,12.501,30.174l156.885,156.885
|
||||
c8.047,8.055,18.765,12.493,30.157,12.493c11.401,0,22.11-4.437,30.174-12.501l253.431-253.773
|
||||
c19.703-19.695,28.1-39.364,28.1-65.766V42.667C511.548,19.14,492.408,0,468.881,0z"/>
|
||||
<path d="M279.398,224.367c-3.336-3.337-8.73-3.337-12.066,0l-4.608,4.608c-6.75-4.471-14.805-7.108-23.492-7.108
|
||||
c-23.526,0-42.667,19.14-42.667,42.667c0,12.715,5.248,22.767,9.822,30.114c4.267,6.852,7.245,16.614,7.245,21.086
|
||||
c0,14.114-11.486,25.6-25.6,25.6s-25.6-11.486-25.6-25.6c0-5.18,1.544-10.172,4.463-14.438c2.662-3.883,1.664-9.199-2.219-11.861
|
||||
c-3.891-2.654-9.199-1.664-11.861,2.219c-4.873,7.117-7.45,15.445-7.45,24.081c0,8.687,2.637,16.742,7.108,23.492l-4.608,4.608
|
||||
c-3.337,3.336-3.337,8.73,0,12.066c1.664,1.664,3.849,2.5,6.033,2.5c2.185,0,4.369-0.836,6.033-2.5l4.608-4.608
|
||||
c6.75,4.471,14.805,7.108,23.492,7.108c23.526,0,42.667-19.14,42.667-42.667c0-8.311-4.13-20.975-9.822-30.114
|
||||
c-5.077-8.149-7.245-14.455-7.245-21.086c0-14.114,11.486-25.6,25.6-25.6c6.844,0,13.03,2.731,17.621,7.125
|
||||
c0.145,0.154,0.196,0.358,0.341,0.512c0.154,0.145,0.358,0.196,0.512,0.341c4.395,4.591,7.125,10.778,7.125,17.621
|
||||
s-2.671,13.286-7.518,18.133c-3.337,3.328-3.337,8.73-0.009,12.066c3.319,3.337,8.73,3.328,12.066,0.009
|
||||
c8.073-8.073,12.527-18.799,12.527-30.208c0-8.687-2.637-16.742-7.108-23.492l4.608-4.608
|
||||
C282.735,233.097,282.735,227.703,279.398,224.367z"/>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
3
resources/js/assets/search-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 50 50">
|
||||
<path d="M 21 3 C 11.621094 3 4 10.621094 4 20 C 4 29.378906 11.621094 37 21 37 C 24.710938 37 28.140625 35.804688 30.9375 33.78125 L 44.09375 46.90625 L 46.90625 44.09375 L 33.90625 31.0625 C 36.460938 28.085938 38 24.222656 38 20 C 38 10.621094 30.378906 3 21 3 Z M 21 5 C 29.296875 5 36 11.703125 36 20 C 36 28.296875 29.296875 35 21 35 C 12.703125 35 6 28.296875 6 20 C 6 11.703125 12.703125 5 21 5 Z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 523 B |
17
resources/js/settingsStore.js
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
import { reactive } from 'vue';
|
||||
|
||||
export const settingsStore = reactive({
|
||||
countries: "",
|
||||
country: "",
|
||||
field: "",
|
||||
online: false,
|
||||
text: "",
|
||||
currency: "EUR",
|
||||
online: false,
|
||||
ccountry_filter: [["Country"]],
|
||||
products_count: {},
|
||||
ccountry_list: [],
|
||||
currencyCoef: 1.0,
|
||||
settings: {},
|
||||
});
|
||||
@@ -4,7 +4,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="Ikea price comparison for Europe">
|
||||
<meta name="keywords" content="ikea, price, comparison, compare, check, scanner, product, ikea price, ikea comparison, price comparison, ikea compare, price compare, vergleichen, preis, preise, ikea preis, ikea preise, ikea vergleichen, ikea vergleichen preis, ikea vergleichen preise, vergleichen preis, vergleichen preise, valeur, products">
|
||||
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<!-- Fonts -->
|
||||
@@ -12,19 +13,37 @@
|
||||
|
||||
<!-- Icons -->
|
||||
<link rel="stylesheet" href="{{ url('/vendors/fontawesome/css/all.min.css') }}">
|
||||
|
||||
<link rel="shortcut icon" href="{{ asset('images/favicon.png') }}">
|
||||
<!-- Scripts -->
|
||||
<style>
|
||||
.gradient {
|
||||
background: linear-gradient(90deg, #d53369 0%, #daae51 100%);
|
||||
}
|
||||
|
||||
</style>
|
||||
@routes
|
||||
@vite('resources/js/app.js')
|
||||
@inertiaHead
|
||||
|
||||
<!-- Matomo -->
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://matomo.soson.eu/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '3']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Matomo Code -->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="font-sans antialiased bg-gray-100">
|
||||
<body class="font-sans antialiased bg-gray-100 relative min-h-screen">
|
||||
@inertia
|
||||
</body>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
html, body {
|
||||
background-color: #fff;
|
||||
color: #636b6f;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; */
|
||||
font-weight: 100;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
|
||||
19
resources/views/mails/feedback.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Feedback Mail From IKEA Price Crawler</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>User Detail:</h3>
|
||||
|
||||
<h4>Name: {{ $data['name'] }}</h4>
|
||||
<h4>Email: {{ $data['email'] }}</h4>
|
||||
<h4>Subject: {{ $data['subject'] }}</h4>
|
||||
|
||||
<hr/>
|
||||
<p>{{ $data['message'] }}</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,9 +5,14 @@ use Inertia\Inertia;
|
||||
use App\Http\Controllers\CountryCodeController;
|
||||
use App\Http\Controllers\CountryCompareController;
|
||||
use App\Http\Controllers\CurrencyRatesController;
|
||||
use App\Http\Controllers\GeoIPController;
|
||||
use App\Http\Controllers\IkeaProductsController;
|
||||
use App\Http\Controllers\ProductsCompareController;
|
||||
|
||||
use App\Http\Controllers\FeedbackController;
|
||||
use App\Http\Controllers\SettingsController;
|
||||
use App\Http\Controllers\OnlineCompareController;
|
||||
use App\Http\Controllers\ProductsCountController;
|
||||
use App\Http\Controllers\LoggingController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -29,21 +34,31 @@ Route::get('/about/', function () {
|
||||
Route::get('/exchange/', function () {
|
||||
return Inertia::render('IkeaExchange');
|
||||
})->name('exchange');
|
||||
Route::get('/doc/', function () {
|
||||
return Inertia::render('IkeaDoc');
|
||||
})->name('doc');
|
||||
|
||||
Route::post('users-send-email', [FeedbackController::class, 'sendEmail'])->name('ajax.send.email');
|
||||
//Route::get('/products/online', [OnlineCompareController::class,'compare'])->name('products.online.compare');
|
||||
Route::get('/products/count', [ProductsCountController::class,'index'])->name('products.count');
|
||||
Route::get('/menu/get', [App\Http\Controllers\Superuser\UserMenuController::class, 'get'])->name('menu.user');
|
||||
|
||||
Route::get('/ip/get/{ip?}', [GeoIPController::class, 'index'])->name('geo.ip.get');
|
||||
Route::get('/ccountry/', [CountryCodeController::class, 'index'])->name('ccountry.index');
|
||||
Route::get('/settings/', [SettingsController::class, 'index'])->name('settings.index');
|
||||
Route::get('/ccountry/codes/', [CountryCodeController::class, 'codes'])->name('ccountry.codes');
|
||||
Route::get('/ccountry/active/', [CountryCodeController::class, 'active'])->name('ccountry.active');
|
||||
Route::get('/search/{id}',[CountryCompareController::class,'search'])->name('ccompare.search');
|
||||
Route::get('/rates/', [CurrencyRatesController::class, 'index'])->name('rates.index');
|
||||
Route::get('/products/{item}/{country?}', [IkeaProductsController::class, 'search'])->name('products.search');
|
||||
Route::get('/products/{field}/{text}/{country?}', [IkeaProductsController::class, 'search'])->name('products.search');
|
||||
Route::post('/products/compare/', [ProductsCompareController::class, 'compare'])->name('products.compare');
|
||||
Route::get('phpmyinfo', function () {
|
||||
phpinfo();
|
||||
})->name('phpmyinfo');
|
||||
|
||||
Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified'])->group(function () {
|
||||
// Route::get('/', function () {
|
||||
// return Inertia::render('Dashboard');
|
||||
// })->name('dashboard');
|
||||
Route::get('/dashboard', function () {
|
||||
return Inertia::render('Dashboard');
|
||||
})->name('dashboard');
|
||||
|
||||
Route::prefix('/superuser')->name('superuser.')->group(function () {
|
||||
Route::resource('permission', App\Http\Controllers\Superuser\PermissionController::class)->only([
|
||||
@@ -77,6 +92,7 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
|
||||
});
|
||||
|
||||
Route::get('/activity/login', [App\Http\Controllers\ActivityController::class, 'login'])->name('activity.login');
|
||||
Route::get('/activity/logging', [App\Http\Controllers\LoggingController::class, 'logging'])->name('activity.logging');
|
||||
|
||||
Route::get('/user/{user}/menu', fn (App\Models\User $user) => $user->menus())->name('user.menu');
|
||||
Route::get('/permission/get', [App\Http\Controllers\Superuser\PermissionController::class, 'get'])->name('permission');
|
||||
@@ -84,6 +100,7 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
|
||||
Route::post('/role/paginate', [App\Http\Controllers\Superuser\RoleController::class, 'paginate'])->name('role.paginate');
|
||||
Route::post('/user/paginate', [App\Http\Controllers\Superuser\UserController::class, 'paginate'])->name('user.paginate');
|
||||
Route::post('/activity/login', [App\Http\Controllers\ActivityController::class, 'logins'])->name('activity.login.post');
|
||||
Route::post('/activity/logging', [App\Http\Controllers\LoggingController::class, 'records'])->name('activity.logging.post');
|
||||
Route::get('/menu/get', [App\Http\Controllers\Superuser\MenuController::class, 'get'])->name('menu');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@@ -15,5 +16,15 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
}),
|
||||
viteCompression(),
|
||||
],
|
||||
build: {
|
||||
assetsInlineLimit: 0
|
||||
},
|
||||
esbuild: {
|
||||
drop: ['console', 'debugger'],
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ["vue"],
|
||||
}
|
||||
});
|
||||
|
||||