mirror of
https://github.com/towfiqi/serpbear
synced 2025-06-26 18:15:54 +00:00
133 lines
6.7 KiB
TypeScript
133 lines
6.7 KiB
TypeScript
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
import dayjs from 'dayjs';
|
|
import Icon from '../common/Icon';
|
|
import countries from '../../utils/countries';
|
|
import Chart from '../common/Chart';
|
|
import SelectField from '../common/SelectField';
|
|
import { useFetchSingleKeyword } from '../../services/keywords';
|
|
import useOnKey from '../../hooks/useOnKey';
|
|
import { generateTheChartData } from '../../utils/client/generateChartData';
|
|
|
|
type KeywordDetailsProps = {
|
|
keyword: KeywordType,
|
|
closeDetails: Function
|
|
}
|
|
|
|
const KeywordDetails = ({ keyword, closeDetails }:KeywordDetailsProps) => {
|
|
const updatedDate = new Date(keyword.lastUpdated);
|
|
const [chartTime, setChartTime] = useState<string>('30');
|
|
const searchResultContainer = useRef<HTMLDivElement>(null);
|
|
const searchResultFound = useRef<HTMLDivElement>(null);
|
|
const { data: keywordData } = useFetchSingleKeyword(keyword.ID);
|
|
const keywordHistory: KeywordHistory = keywordData?.history || keyword.history;
|
|
const keywordSearchResult: KeywordLastResult = keywordData?.searchResult || keyword.history;
|
|
const dateOptions = [
|
|
{ label: 'Last 7 Days', value: '7' },
|
|
{ label: 'Last 30 Days', value: '30' },
|
|
{ label: 'Last 90 Days', value: '90' },
|
|
{ label: '1 Year', value: '360' },
|
|
{ label: 'All Time', value: 'all' },
|
|
];
|
|
|
|
useOnKey('Escape', closeDetails);
|
|
|
|
useLayoutEffect(() => {
|
|
if (keyword.position < 100 && keyword.position > 0 && searchResultFound?.current) {
|
|
searchResultFound.current.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'center',
|
|
inline: 'start',
|
|
});
|
|
}
|
|
}, [keywordSearchResult, keyword.position]);
|
|
|
|
const chartData = useMemo(() => {
|
|
return generateTheChartData(keywordHistory, chartTime);
|
|
}, [keywordHistory, chartTime]);
|
|
|
|
const closeOnBGClick = (e:React.SyntheticEvent) => {
|
|
e.stopPropagation();
|
|
e.nativeEvent.stopImmediatePropagation();
|
|
if (e.target === e.currentTarget) { closeDetails(); }
|
|
};
|
|
|
|
return (
|
|
<div className="keywordDetails fixed w-full h-screen top-0 left-0 z-[99999]" onClick={closeOnBGClick} data-testid="keywordDetails">
|
|
<div className="keywordDetails absolute w-full lg:w-5/12 bg-white customShadow top-0 right-0 h-screen" >
|
|
<div className='keywordDetails__header p-6 border-b border-b-slate-200 text-slate-600'>
|
|
<h3 className=' text-lg font-bold'>
|
|
<span title={countries[keyword.country][0]}
|
|
className={`fflag fflag-${keyword.country} w-[18px] h-[12px] mr-2`} /> {keyword.keyword}
|
|
<span
|
|
className={`py-1 px-2 ml-2 rounded bg-blue-50 ${keyword.position === 0 ? 'text-gray-500' : 'text-blue-700'} text-xs font-bold`}>
|
|
{keyword.position === 0 ? 'Not in First 100' : keyword.position}
|
|
</span>
|
|
</h3>
|
|
<button
|
|
className='absolute top-2 right-2 p-2 px-3 text-gray-400 hover:text-gray-700 transition-all hover:rotate-90'
|
|
onClick={() => closeDetails()}>
|
|
<Icon type='close' size={24} />
|
|
</button>
|
|
</div>
|
|
<div className='keywordDetails__content p-6'>
|
|
|
|
<div className='keywordDetails__section'>
|
|
<div className="keywordDetails__section__head flex justify-between mb-5">
|
|
<h3 className=' font-bold text-gray-700 text-lg'>SERP History</h3>
|
|
<div className="keywordDetails__section__chart_select mr-3">
|
|
<SelectField
|
|
options={dateOptions}
|
|
selected={[chartTime]}
|
|
defaultLabel="Select Date"
|
|
updateField={(updatedTime:[string]) => setChartTime(updatedTime[0])}
|
|
multiple={false}
|
|
rounded={'rounded'}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className='keywordDetails__section__chart h-64'>
|
|
<Chart labels={chartData.labels} sreies={chartData.sreies} />
|
|
</div>
|
|
</div>
|
|
<div className='keywordDetails__section mt-10'>
|
|
<div className="keywordDetails__section__head flex justify-between items-center pb-4 mb-4 border-b border-b-slate-200">
|
|
<h3 className=' font-bold text-gray-700 lg:text-lg'>Google Search Result
|
|
<a className='text-gray-400 hover:text-indigo-600 inline-block ml-1 px-2 py-1'
|
|
href={`https://www.google.com/search?q=${encodeURI(keyword.keyword)}`}
|
|
target="_blank"
|
|
rel='noreferrer'>
|
|
<Icon type='link' size={14} />
|
|
</a>
|
|
</h3>
|
|
<span className=' text-xs text-gray-500'>{dayjs(updatedDate).format('MMMM D, YYYY')}</span>
|
|
</div>
|
|
<div className='keywordDetails__section__results styled-scrollbar overflow-y-auto' ref={searchResultContainer}>
|
|
{keywordSearchResult && Array.isArray(keywordSearchResult) && keywordSearchResult.length > 0 && (
|
|
keywordSearchResult.map((item, index) => {
|
|
const { position } = keyword;
|
|
const domainExist = position < 100 && index === (position - 1);
|
|
return (
|
|
<div
|
|
ref={domainExist ? searchResultFound : null}
|
|
className={`leading-6 mb-4 mr-3 p-3 text-sm break-all pr-3 rounded
|
|
${domainExist ? ' bg-amber-50 border border-amber-200' : ''}`}
|
|
key={item.url + item.position}>
|
|
<h4 className='font-semibold text-blue-700'>
|
|
<a href={item.url} target="_blank" rel='noreferrer'>{`${index + 1}. ${item.title}`}</a>
|
|
</h4>
|
|
{/* <p>{item.description}</p> */}
|
|
<a className=' text-green-900' href={item.url} target="_blank" rel='noreferrer'>{item.url}</a>
|
|
</div>
|
|
);
|
|
})
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default KeywordDetails;
|