diff --git a/.eslintrc.json b/.eslintrc.json index db9c514..23981d6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,6 +14,8 @@ "max-len": ["error", {"code": 150, "ignoreComments": true, "ignoreUrls": true}], "import/no-extraneous-dependencies": "off", "no-unused-vars": "off", + "implicit-arrow-linebreak": "off", + "function-paren-newline": "off", "import/extensions": [ "error", "ignorePackages", diff --git a/components/common/Icon.tsx b/components/common/Icon.tsx index 3f8d35d..f292d18 100644 --- a/components/common/Icon.tsx +++ b/components/common/Icon.tsx @@ -10,10 +10,10 @@ type IconProps = { } const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '' }: IconProps) => { - const xmlnsProps = { xmlns: 'http://www.w3.org/2000/svg', xmlnsXlink: 'http://www.w3.org/1999/xlink', preserveAspectRatio: 'xMidYMid meet' }; + const xmlnsProps = { title, xmlns: 'http://www.w3.org/2000/svg', xmlnsXlink: 'http://www.w3.org/1999/xlink', preserveAspectRatio: 'xMidYMid meet' }; return ( - + {type === 'logo' && @@ -308,6 +308,11 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = ' } + {type === 'lock' + && + + + } ); }; diff --git a/components/keywords/Keyword.tsx b/components/keywords/Keyword.tsx index 23eb89c..ed63cde 100644 --- a/components/keywords/Keyword.tsx +++ b/components/keywords/Keyword.tsx @@ -21,7 +21,9 @@ type KeywordProps = { lastItem?:boolean, showSCData: boolean, scDataType: string, - style: Object + style: Object, + maxTitleColumnWidth: number, + tableColumns? : string[] } const Keyword = (props: KeywordProps) => { @@ -39,12 +41,16 @@ const Keyword = (props: KeywordProps) => { style, index, scDataType = 'threeDays', + tableColumns = [], + maxTitleColumnWidth, } = props; const { keyword, domain, ID, city, position, url = '', lastUpdated, country, sticky, history = {}, updating = false, lastUpdateError = false, volume, } = keywordData; + const [showOptions, setShowOptions] = useState(false); const [showPositionError, setPositionError] = useState(false); + const turncatedURL = useMemo(() => { return url.replace(`https://${domain}`, '').replace(`https://www.${domain}`, '').replace(`http://${domain}`, ''); }, [url, domain]); @@ -91,7 +97,7 @@ const Keyword = (props: KeywordProps) => { className={`keyword relative py-5 px-4 text-gray-600 border-b-[1px] border-gray-200 lg:py-4 lg:px-6 lg:border-0 lg:flex lg:justify-between lg:items-center ${selected ? ' bg-indigo-50 keyword--selected' : ''} ${lastItem ? 'border-b-0' : ''}`}> -
+
showKeywordDetails()}> + style={{ maxWidth: `${maxTitleColumnWidth - 35}px` }} + className={'py-2 hover:text-blue-600 lg:flex lg:items-center w-full'} + onClick={() => showKeywordDetails()} + title={keyword} + > - {keyword}{city ? ` (${city})` : ''} + + {keyword}{city ? ` (${city})` : ''} + {sticky && } {lastUpdateError && lastUpdateError.date @@ -126,13 +137,15 @@ const Keyword = (props: KeywordProps) => { ? new Date(bestPosition.date).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' }) : '' } className={`keyword_best hidden bg-[#f8f9ff] w-fit min-w-[50px] h-12 p-2 text-base mt-[-20px] rounded right-5 lg:relative lg:block - lg:bg-transparent lg:w-auto lg:h-auto lg:mt-0 lg:p-0 lg:text-sm lg:flex-1 lg:basis-16 lg:grow-0 lg:right-0 text-center font-semibold`}> + lg:bg-transparent lg:w-auto lg:h-auto lg:mt-0 lg:p-0 lg:text-sm lg:flex-1 lg:basis-16 lg:grow-0 lg:right-0 text-center font-semibold + ${!tableColumns.includes('Best') ? 'lg:hidden' : ''} + `}> {bestPosition ? bestPosition.position || '-' : (position || '-')}
{chartData.labels.length > 0 && (
showKeywordDetails()}>
@@ -140,26 +153,28 @@ const Keyword = (props: KeywordProps) => {
+ overflow-hidden text-ellipsis whitespace-nowrap lg:max-w-none lg:pr-5 lg:pl-3`}> {turncatedURL || '-'}
+ className='inline-block mt-[4] top-[-5px] relative lg:flex-1 lg:m-0 lg:top-0 max-w-[150px]'>
- {showSCData && ( -
SC Position: diff --git a/components/keywords/KeywordFilter.tsx b/components/keywords/KeywordFilter.tsx index 029efe1..1c6da6d 100644 --- a/components/keywords/KeywordFilter.tsx +++ b/components/keywords/KeywordFilter.tsx @@ -15,6 +15,8 @@ type KeywordFilterProps = { integratedConsole?: boolean, isConsole?: boolean, SCcountries?: string[]; + updateColumns?: Function, + tableColumns?: string[] } const KeywordFilters = (props: KeywordFilterProps) => { @@ -29,10 +31,13 @@ const KeywordFilters = (props: KeywordFilterProps) => { filterParams, isConsole = false, integratedConsole = false, + updateColumns, SCcountries = [], + tableColumns = [], } = props; const [sortOptions, showSortOptions] = useState(false); const [filterOptions, showFilterOptions] = useState(false); + const [columnOptions, showColumnOptions] = useState(false); const keywordCounts = useMemo(() => { const counts = { desktop: 0, mobile: 0 }; @@ -84,6 +89,17 @@ const KeywordFilters = (props: KeywordFilterProps) => { { value: 'vol_asc', label: 'Lowest Search Volume' }, { value: 'vol_desc', label: 'Highest Search Volume' }, ]; + + const columnOptionChoices: {label: string, value: string, locked: boolean}[] = [ + { value: 'Keyword', label: 'Keyword', locked: true }, + { value: 'Position', label: 'Position', locked: true }, + { value: 'URL', label: 'URL', locked: true }, + { value: 'Updated', label: 'Updated', locked: true }, + { value: 'Best', label: 'Best', locked: false }, + { value: 'History', label: 'History', locked: false }, + { value: 'Volume', label: 'Volume', locked: false }, + { value: 'Search Console', label: 'Search Console', locked: false }, + ]; if (integratedConsole) { sortOptionChoices.push({ value: 'imp_desc', label: `Most Viewed${isConsole ? ' (Default)' : ''}` }); sortOptionChoices.push({ value: 'imp_asc', label: 'Least Viewed' }); @@ -190,6 +206,43 @@ const KeywordFilters = (props: KeywordFilterProps) => { )}
+ {!isConsole && ( +
+ + {columnOptions && ( +
    + {columnOptionChoices.map(({ value, label, locked }) => { + return
  • { if (updateColumns) { updateColumns(value); } showColumnOptions(false); }} + > + + + + {' '}{label} + +
  • ; + })} +
+ )} +
+ )}
); diff --git a/components/keywords/KeywordsTable.tsx b/components/keywords/KeywordsTable.tsx index 7169201..e160f97 100644 --- a/components/keywords/KeywordsTable.tsx +++ b/components/keywords/KeywordsTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react'; +import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; import { filterKeywords, keywordsByDevice, sortKeywords } from '../../utils/client/sortFilter'; @@ -12,6 +12,8 @@ import KeywordTagManager from './KeywordTagManager'; import AddTags from './AddTags'; import useWindowResize from '../../hooks/useWindowResize'; import useIsMobile from '../../hooks/useIsMobile'; +import { useUpdateSettings } from '../../services/settings'; +import { defaultSettings } from '../settings/Settings'; type KeywordsTableProps = { domain: DomainType | null, @@ -20,10 +22,12 @@ type KeywordsTableProps = { showAddModal: boolean, setShowAddModal: Function, isConsoleIntegrated: boolean, + settings?: SettingsType } const KeywordsTable = (props: KeywordsTableProps) => { - const { keywords = [], isLoading = true, isConsoleIntegrated = false } = props; + const titleColumnRef = useRef(null); + const { keywords = [], isLoading = true, isConsoleIntegrated = false, settings } = props; const showSCData = isConsoleIntegrated; const [device, setDevice] = useState('desktop'); const [selectedKeywords, setSelectedKeywords] = useState([]); @@ -36,11 +40,27 @@ const KeywordsTable = (props: KeywordsTableProps) => { const [sortBy, setSortBy] = useState('date_asc'); const [scDataType, setScDataType] = useState('threeDays'); const [showScDataTypes, setShowScDataTypes] = useState(false); + const [maxTitleColumnWidth, setMaxTitleColumnWidth] = useState(235); const { mutate: deleteMutate } = useDeleteKeywords(() => {}); const { mutate: favoriteMutate } = useFavKeywords(() => {}); const { mutate: refreshMutate } = useRefreshKeywords(() => {}); const [isMobile] = useIsMobile(); - useWindowResize(() => setSCListHeight(window.innerHeight - (isMobile ? 200 : 400))); + + useWindowResize(() => { + setSCListHeight(window.innerHeight - (isMobile ? 200 : 400)); + if (titleColumnRef.current) { + setMaxTitleColumnWidth((titleColumnRef.current as HTMLElement).clientWidth); + } + }); + + useEffect(() => { + if (titleColumnRef.current) { + setMaxTitleColumnWidth((titleColumnRef.current as HTMLElement).clientWidth); + } + }, [titleColumnRef]); + + const tableColumns = settings?.keywordsColumns || ['Best', 'History', 'Volume', 'Search Console']; + const { mutate: updateMutate, isLoading: isUpdatingSettings } = useUpdateSettings(() => console.log('')); const scDataObject:{ [k:string] : string} = { threeDays: 'Last Three Days', @@ -71,6 +91,16 @@ const KeywordsTable = (props: KeywordsTableProps) => { } setSelectedKeywords(updatedSelectd); }; + + const updateColumns = (column:string) => { + const newColumns = tableColumns.includes(column) ? tableColumns.filter((col) => col !== column) : [...tableColumns, column]; + updateMutate({ ...defaultSettings, ...settings, keywordsColumns: newColumns }); + }; + + const shouldHideColumn = useCallback((col:string) => { + return settings?.keywordsColumns && !settings?.keywordsColumns.includes(col) ? 'lg:hidden' : ''; + }, [settings?.keywordsColumns]); + const Row = ({ data, index, style }:ListChildComponentProps) => { const keyword = data[index]; return ( @@ -89,6 +119,8 @@ const KeywordsTable = (props: KeywordsTableProps) => { lastItem={index === (processedKeywords[device].length - 1)} showSCData={showSCData} scDataType={scDataType} + tableColumns={tableColumns} + maxTitleColumnWidth={maxTitleColumnWidth} /> ); }; @@ -136,15 +168,19 @@ const KeywordsTable = (props: KeywordsTableProps) => { keywords={keywords} device={device} setDevice={setDevice} + updateColumns={updateColumns} + tableColumns={tableColumns} integratedConsole={isConsoleIntegrated} /> )} -