diff --git a/package.json b/package.json index e3a2f172..113bae0d 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,8 @@ "@fortawesome/fontawesome-free": "^6.5.1", "@lottiefiles/svelte-lottie-player": "^0.3.1", "@mapbox/geojson-rewind": "^0.5.2", + "@tanstack/match-sorter-utils": "^8.11.8", + "@tanstack/svelte-table": "^8.13.2", "@zerodevx/svelte-toast": "^0.9.5", "axios": "1.6.7", "axios-retry": "^4.0.0", diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 585bbc72..cb2c9ebf 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -18,7 +18,8 @@ const contributeDropdownLinks = [ { title: 'Add Location', url: '/add-location', icon: 'add' }, { title: 'Verify Location', url: '/verify-location', icon: 'verify' }, - { title: 'Open Tickets', url: '/tickets', icon: 'ticket' } + { title: 'Open Tickets', url: '/tickets', icon: 'ticket' }, + { title: 'Tagging Issues', url: '/tagging-issues', icon: 'issue' } ]; const statsDropdownLinks = [ @@ -64,7 +65,7 @@ title={link.title} links={contributeDropdownLinks} top="add" - bottom="ticket" + bottom="issue" /> diff --git a/src/components/IssueCell.svelte b/src/components/IssueCell.svelte new file mode 100644 index 00000000..4e06e427 --- /dev/null +++ b/src/components/IssueCell.svelte @@ -0,0 +1,32 @@ + + +{#if id === 'icon'} + +{:else if id === 'name'} + {value} +{:else if id === 'type'} + {value} +{:else if id === 'viewLink'} + + View + +{:else if id === 'editLink'} + + Edit + +{/if} diff --git a/src/components/IssueIcon.svelte b/src/components/IssueIcon.svelte new file mode 100644 index 00000000..4091ccdf --- /dev/null +++ b/src/components/IssueIcon.svelte @@ -0,0 +1,57 @@ + + +
+ {#if icon === 'fa-calendar-days'} + + {:else if icon === 'fa-clipboard-question'} + + {:else if icon === 'fa-hourglass-end'} + + {:else if icon === 'fa-icons'} + + {:else if icon === 'fa-list-check'} + + {:else if icon === 'fa-spell-check'} + + {:else if icon === 'fa-hourglass-half'} + + {:else} + + {/if} +
diff --git a/src/components/IssuesTable.svelte b/src/components/IssuesTable.svelte new file mode 100644 index 00000000..f7b5c56e --- /dev/null +++ b/src/components/IssuesTable.svelte @@ -0,0 +1,379 @@ + + +
+
+

+ {title} +

+ + {#if loading} +
+
+ +
+
+ {:else if !issues.length} +

No tagging issues!

+ {:else if $table} +
+ + {#if globalFilter} + + {/if} +
+ {#if $table.getFilteredRowModel().rows.length === 0} +

No results found.

+ {:else} +
+ + + {#each $table.getHeaderGroups() as headerGroup} + + {#each headerGroup.headers as header} + + {/each} + + {/each} + + + {#each $table.getRowModel().rows as row, index} + + {#each row.getVisibleCells() as cell} + + {/each} + + {/each} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ +
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+ + +
Page
+ + {$table?.getState().pagination.pageIndex + 1} of + {$table?.getPageCount().toLocaleString()} + +
+
+
+ {/if} + {/if} +
+
+ +{#if typeof window !== 'undefined'} + {#if detectTheme() === 'dark' || $theme === 'dark'} + + {/if} +{/if} diff --git a/src/components/MerchantButton.svelte b/src/components/MerchantButton.svelte index bd11ab7e..ea30b58a 100644 --- a/src/components/MerchantButton.svelte +++ b/src/components/MerchantButton.svelte @@ -6,7 +6,7 @@ + + ` : '' } @@ -1118,6 +1136,14 @@ ${ }; } + const taggingIssuesButton: HTMLButtonElement | null = + popupContainer.querySelector('#tagging-issues'); + if (taggingIssuesButton) { + taggingIssuesButton.onclick = () => { + taggingIssues.set(issues || []); + }; + } + const boostButton: HTMLButtonElement | null = popupContainer.querySelector('#boost-button'); const boostButtonText: HTMLSpanElement | null | undefined = boostButton?.querySelector('span'); diff --git a/src/lib/store.ts b/src/lib/store.ts index 3a3ffd1e..daa84d74 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,4 +1,4 @@ -import type { Area, Boost, Element, Event, OSMTags, Report, Theme, User } from '$lib/types'; +import type { Area, Boost, Element, Event, Issue, OSMTags, Report, Theme, User } from '$lib/types'; import { readable, writable, type Writable } from 'svelte/store'; export const socials = readable({ @@ -72,6 +72,7 @@ export const resetBoost = writable(0); export const boostHash: Writable = writable(); export const showTags: Writable = writable(); +export const taggingIssues: Writable = writable(); export const showMore = writable(false); export const theme: Writable = writable(); diff --git a/src/lib/types.ts b/src/lib/types.ts index a2880abd..e75a0722 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -68,12 +68,27 @@ export type Element = { ['payment:uri']?: string; ['payment:coinos']?: string; ['payment:pouch']?: string; + issues?: Issue[]; }; created_at: string; updated_at: string; deleted_at: string; }; +export type IssueType = + | 'date_format' + | 'misspelled_tag' + | 'missing_icon' + | 'not_verified' + | 'out_of_date' + | 'out_of_date_soon'; + +export type Issue = { + description: string; + severity: number; + type: IssueType; +}; + export type ElementOSM = { type: 'node' | 'way' | 'relation'; id: number; @@ -224,6 +239,24 @@ export interface ActivityEvent extends Event { tagger?: User; } +// issues + +interface IssueExtended extends Issue { + merchantName: string | undefined; + merchantId: string; +} + +export type Issues = IssueExtended[]; + +export type IssueIcon = + | 'fa-calendar-days' + | 'fa-spell-check' + | 'fa-icons' + | 'fa-clipboard-question' + | 'fa-hourglass-end' + | 'fa-list-check' + | 'fa-hourglass-half'; + // misc export type Theme = 'light' | 'dark'; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b6ea5468..9e4fda14 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,8 +1,8 @@ import { theme } from '$lib/store'; +import type { Element, Grade, IssueIcon, IssueType, Issues } from '$lib/types'; import { toast } from '@zerodevx/svelte-toast'; import type { Chart } from 'chart.js'; import { get } from 'svelte/store'; -import type { Grade } from './types'; export const errToast = (m: string) => { toast.push(m, { @@ -97,3 +97,52 @@ export const getGrade = (upToDatePercent: number): Grade => { return 1; } }; + +export const getIssues = (elements: Element[]): Issues => { + const issues: Issues = []; + + elements.forEach((element) => { + if (!element.tags.issues?.length) return; + + element.tags.issues.forEach((issue) => { + issues.push({ ...issue, merchantName: element.osm_json.tags?.name, merchantId: element.id }); + }); + }); + + return issues; +}; + +export const getIssueIcon = (type: IssueType): IssueIcon => { + switch (type) { + case 'date_format': + return 'fa-calendar-days'; + case 'misspelled_tag': + return 'fa-spell-check'; + case 'missing_icon': + return 'fa-icons'; + case 'not_verified': + return 'fa-clipboard-question'; + case 'out_of_date': + return 'fa-hourglass-end'; + case 'out_of_date_soon': + return 'fa-hourglass-half'; + default: + return 'fa-list-check'; + } +}; + +export const isEven = (number: number) => { + return number % 2 === 0; +}; + +export function debounce(func: (e?: any) => void, timeout = 500) { + let timer: ReturnType; + // @ts-expect-error + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => { + // @ts-expect-error + func.apply(this, args); + }, timeout); + }; +} diff --git a/src/routes/community/[area]/+page.svelte b/src/routes/community/[area]/+page.svelte index b837683e..8a4df53c 100644 --- a/src/routes/community/[area]/+page.svelte +++ b/src/routes/community/[area]/+page.svelte @@ -7,6 +7,7 @@ Footer, Header, InfoTooltip, + IssuesTable, LatestTagger, MapLoadingEmbed, OpenTicket, @@ -50,12 +51,21 @@ type BaseMaps, type Continents, type DomEventType, + type Element, type Event, type Grade, + type Issues, type Leaflet, type User } from '$lib/types.js'; - import { detectTheme, errToast, formatElementID, getGrade, updateChartThemes } from '$lib/utils'; + import { + detectTheme, + errToast, + formatElementID, + getGrade, + getIssues, + updateChartThemes + } from '$lib/utils'; // @ts-expect-error import rewind from '@mapbox/geojson-rewind'; import Chart from 'chart.js/auto'; @@ -147,7 +157,7 @@ const rewoundPoly = rewind(community.geo_json, true); // filter elements within community - const filteredElements = $elements.filter((element) => { + const filteredElements: Element[] = $elements.filter((element) => { let lat = latCalc(element['osm_json']); let long = longCalc(element['osm_json']); @@ -347,6 +357,8 @@ grade = getGrade(Number(upToDatePercent)); + issues = getIssues(filteredElements); + const populateCharts = () => { const chartsReports = [...communityReports].sort( (a, b) => Date.parse(a['created_at']) - Date.parse(b['created_at']) @@ -804,6 +816,8 @@ let baseMaps: BaseMaps; + let issues: Issues = []; + let chartsLoading = true; let upToDateChartCanvas: HTMLCanvasElement; let upToDateChart: Chart<'line', number[], string>; @@ -1215,6 +1229,12 @@ + +
diff --git a/src/routes/map/+page.svelte b/src/routes/map/+page.svelte index dbdf69bb..7ae5c6ac 100644 --- a/src/routes/map/+page.svelte +++ b/src/routes/map/+page.svelte @@ -1,7 +1,7 @@ + + + BTC Map - Tagging Issues + + + + + +
+
+
+
+ {#if typeof window !== 'undefined'} +

+ Tagging Issues +

+ {:else} + + {/if} + +

+ Contribute to THE map by resolving tagging issues! +

+ +

+ More information about how to get involved can be found on our Tagging Instructions + Wiki page. +

+ + +
+ +
+
+
diff --git a/static/icons/mobile-nav/spritesheet.svg b/static/icons/mobile-nav/spritesheet.svg index e54e1d43..4eddcb2f 100644 --- a/static/icons/mobile-nav/spritesheet.svg +++ b/static/icons/mobile-nav/spritesheet.svg @@ -43,6 +43,9 @@ + + + diff --git a/static/icons/popup/spritesheet.svg b/static/icons/popup/spritesheet.svg index effb6af6..1c320c1e 100644 --- a/static/icons/popup/spritesheet.svg +++ b/static/icons/popup/spritesheet.svg @@ -41,6 +41,9 @@ + + + diff --git a/yarn.lock b/yarn.lock index d5cb6fab..8ebd3484 100644 --- a/yarn.lock +++ b/yarn.lock @@ -401,6 +401,25 @@ svelte-hmr "^0.15.3" vitefu "^0.2.5" +"@tanstack/match-sorter-utils@^8.11.8": + version "8.11.8" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.11.8.tgz#9132c2a21cf18ca2f0071b604ddadb7a66e73367" + integrity sha512-3VPh0SYMGCa5dWQEqNab87UpCMk+ANWHDP4ALs5PeEW9EpfTAbrezzaOk/OiM52IESViefkoAOYuxdoa04p6aA== + dependencies: + remove-accents "0.4.2" + +"@tanstack/svelte-table@^8.13.2": + version "8.13.2" + resolved "https://registry.yarnpkg.com/@tanstack/svelte-table/-/svelte-table-8.13.2.tgz#716ef0ba8f2bc111746e3d0c398d09fd6292c1af" + integrity sha512-lA9xSVWBDFPy7cDH6bhA2OX8AZ5bZEcuG9+VBGLmGpCg78kXjr3VZMGaWUD+rTJM42Fp6zOmp1E1McSKVhUGww== + dependencies: + "@tanstack/table-core" "8.13.2" + +"@tanstack/table-core@8.13.2": + version "8.13.2" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.13.2.tgz#2512574dd3d20dc94b7db1f9f48090f0c18b5c85" + integrity sha512-/2saD1lWBUV6/uNAwrsg2tw58uvMJ07bO2F1IWMxjFRkJiXKQRuc3Oq2aufeobD3873+4oIM/DRySIw7+QsPPw== + "@types/cookie@^0.6.0": version "0.6.0" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" @@ -2054,6 +2073,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"