From f8e0376ddd0566a06778e3e08d051520cea230f2 Mon Sep 17 00:00:00 2001 From: Christian Kellner Date: Wed, 3 Sep 2025 14:22:04 +0200 Subject: [PATCH] adding new chart system --- .eslintignore | 3 - eslint.config.js | 75 ++++--------------- package.json | 2 +- ui/src/services/time/timeService.js | 2 +- ui/src/views/jobs/insights/JobInsight.jsx | 16 ++-- ui/src/views/jobs/insights/Linechart.jsx | 16 +++- .../components/provider/ProviderMutator.jsx | 3 +- ui/src/views/login/Login.jsx | 3 +- 8 files changed, 44 insertions(+), 76 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 86c15b9..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -/ui/public -/db/ -/conf/ \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index 008dfd0..9114827 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,6 +6,10 @@ import react from 'eslint-plugin-react'; import babelParser from '@babel/eslint-parser'; export default [ + { + files: ['**/*.{js,jsx,ts,tsx}'], + ignores: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/public/**', 'db/**', 'conf/**'], + }, js.configs.recommended, prettier, { @@ -23,70 +27,34 @@ export default [ after: 'readonly', it: 'readonly', }, - parserOptions: { - requireConfigFile: false, - }, - }, - plugins: { - react, + parserOptions: { requireConfigFile: false }, }, + plugins: { react }, rules: { eqeqeq: [2, 'allow-null'], - - // Semantics / Performance impacting strict: 0, 'no-redeclare': [2, { builtinGlobals: false }], 'class-methods-use-this': 'off', - - // Style indent: ['off', 2], 'linebreak-style': ['error', 'unix'], quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], semi: ['error', 'always'], 'no-console': ['error', { allow: ['warn', 'error'] }], - - // React 'jsx-quotes': ['error', 'prefer-double'], 'react/display-name': 'off', 'react/forbid-prop-types': 'off', 'react/jsx-closing-bracket-location': 'off', 'react/jsx-curly-spacing': 'off', - 'react/jsx-handler-names': [ - 'off', - { - eventHandlerPrefix: 'handle', - eventHandlerPropPrefix: 'on', - }, - ], + 'react/jsx-handler-names': ['off', { eventHandlerPrefix: 'handle', eventHandlerPropPrefix: 'on' }], 'react/jsx-indent-props': 'off', 'react/jsx-key': 'off', 'react/jsx-max-props-per-line': 'off', - 'react/jsx-no-bind': [ - 'error', - { - ignoreRefs: true, - allowArrowFunctions: true, - allowBind: false, - }, - ], + 'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true, allowBind: false }], 'react/jsx-no-duplicate-props': ['error', { ignoreCase: true }], 'react/jsx-no-literals': 'off', 'react/jsx-no-undef': 'error', - 'react/jsx-pascal-case': [ - 'error', - { - allowAllCaps: true, - ignore: [], - }, - ], - 'react/sort-prop-types': [ - 'off', - { - ignoreCase: true, - callbacksLast: false, - requiredFirst: false, - }, - ], + 'react/jsx-pascal-case': ['error', { allowAllCaps: true, ignore: [] }], + 'react/sort-prop-types': ['off', { ignoreCase: true, callbacksLast: false, requiredFirst: false }], 'react/jsx-sort-prop-types': 'off', 'react/jsx-sort-props': 'off', 'react/jsx-uses-react': 'error', @@ -106,14 +74,7 @@ export default [ 'react/require-render-return': 'error', 'react/self-closing-comp': 'warn', 'react/sort-comp': 'off', - 'react/jsx-wrap-multilines': [ - 'warn', - { - declaration: true, - assignment: true, - return: true, - }, - ], + 'react/jsx-wrap-multilines': ['warn', { declaration: true, assignment: true, return: true }], 'react/wrap-multilines': 'off', 'react/jsx-first-prop-new-line': 'off', 'react/jsx-equals-spacing': ['warn', 'never'], @@ -126,20 +87,10 @@ export default [ 'react/no-find-dom-node': 'warn', 'react/forbid-component-props': ['off', { forbid: [] }], 'react/no-danger-with-children': 'error', - 'react/no-unused-prop-types': [ - 'warn', - { - customValidators: [], - skipShapeProps: true, - }, - ], + 'react/no-unused-prop-types': ['warn', { customValidators: [], skipShapeProps: true }], 'react/style-prop-object': 'error', 'react/no-children-prop': 'warn', }, - settings: { - react: { - version: 'detect', - }, - }, + settings: { react: { version: 'detect' } }, }, ]; diff --git a/package.json b/package.json index 840f2c7..35d9c2f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "11.4.4", + "version": "11.5.0", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", diff --git a/ui/src/services/time/timeService.js b/ui/src/services/time/timeService.js index 5801d22..ac1fbd7 100644 --- a/ui/src/services/time/timeService.js +++ b/ui/src/services/time/timeService.js @@ -8,4 +8,4 @@ export function format(ts) { second: 'numeric', }).format(ts); } -export const roundToNext5Minute = (ts) => Math.ceil(ts / (1000 * 60 * 5)) * (1000 * 60 * 5); +export const roundToHour = (ts) => Math.ceil(ts / (1000 * 60 * 60)) * (1000 * 60 * 60); diff --git a/ui/src/views/jobs/insights/JobInsight.jsx b/ui/src/views/jobs/insights/JobInsight.jsx index f5ce3b3..507cc85 100644 --- a/ui/src/views/jobs/insights/JobInsight.jsx +++ b/ui/src/views/jobs/insights/JobInsight.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { roundToNext5Minute } from '../../../services/time/timeService'; +import { roundToHour } from '../../../services/time/timeService'; import Headline from '../../../components/headline/Headline'; import { useDispatch, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; @@ -26,14 +26,13 @@ const JobInsight = function JobInsight() { const allTimes = new Set(); const cap = (s) => (s ? s[0].toUpperCase() + s.slice(1) : 'Unknown'); - const toDate = (ts) => new Date(ts < 1e12 ? ts * 1000 : ts); // seconds vs ms providers.forEach((key) => { const providerName = cap(key); const tmpTimeObj = {}; Object.values(data[key] || {}).forEach((listingTs) => { - const time = roundToNext5Minute(listingTs); + const time = roundToHour(listingTs); tmpTimeObj[time] = tmpTimeObj[time] == null ? 1 : tmpTimeObj[time] + 1; allTimes.add(time); }); @@ -50,15 +49,20 @@ const JobInsight = function JobInsight() { sortedTimes.forEach((t) => { result.push({ - listings: Number(t), // Date object for time axis + listings: new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }).format(new Date(parseInt(t))), listingsNumber: bucket[t] || 0, // y value provider: providerName, // series key }); }); }); - console.log(result); - return result; }; diff --git a/ui/src/views/jobs/insights/Linechart.jsx b/ui/src/views/jobs/insights/Linechart.jsx index 8ca213a..3eac812 100644 --- a/ui/src/views/jobs/insights/Linechart.jsx +++ b/ui/src/views/jobs/insights/Linechart.jsx @@ -11,6 +11,21 @@ const commonSpec = { yField: 'listingsNumber', seriesField: 'provider', legends: { visible: true }, + line: { + style: { + lineWidth: 2, + }, + }, + point: { + visible: false, + }, + axes: [ + { + orient: 'bottom', + field: 'listings', + zero: false, + }, + ], }; const Linechart = function Linechart({ title, series, isLoading = false }) { @@ -28,7 +43,6 @@ const Linechart = function Linechart({ title, series, isLoading = false }) { }, data: { values: series }, }} - option={{ mode: 'desktop-browser' }} /> )} diff --git a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx index e7990f3..e7865b2 100644 --- a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx +++ b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx @@ -30,7 +30,8 @@ export default function ProviderMutator({ onVisibilityChanged, visible = false, if (selectedProvider.baseUrl.indexOf(url.origin) === -1) { return 'The url you have copied is not valid.'; } - } catch (Exception) { + /* eslint-disable no-unused-vars */ + } catch (ignored) { return 'The url you have copied is not valid.'; } return null; diff --git a/ui/src/views/login/Login.jsx b/ui/src/views/login/Login.jsx index f25c793..4e22a23 100644 --- a/ui/src/views/login/Login.jsx +++ b/ui/src/views/login/Login.jsx @@ -38,7 +38,8 @@ export default function Login() { username: username.trim(), password, }); - } catch (Exception) { + /* eslint-disable no-unused-vars */ + } catch (ignored) { Toast.error('Login unsuccessful…'); return; }