Skip to content

Commit

Permalink
feat: Adds ability to pick existing tags when applying keyword tags.
Browse files Browse the repository at this point in the history
closes #171
  • Loading branch information
towfiqi committed Mar 1, 2024
1 parent 2a1fc0e commit 407ab8d
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 14 deletions.
41 changes: 36 additions & 5 deletions components/keywords/AddKeywords.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useMemo, useRef, useState } from 'react';
import Icon from '../common/Icon';
import Modal from '../common/Modal';
import SelectField from '../common/SelectField';
Expand All @@ -24,10 +24,16 @@ type KeywordsInput = {

const AddKeywords = ({ closeModal, domain, keywords, scraperName = '', allowsCity = false }: AddKeywordsProps) => {
const [error, setError] = useState<string>('');
const [showTagSuggestions, setShowTagSuggestions] = useState(false);
const inputRef = useRef(null);
const defCountry = localStorage.getItem('default_country') || 'US';
const [newKeywordsData, setNewKeywordsData] = useState<KeywordsInput>({ keywords: '', device: 'desktop', country: defCountry, domain, tags: '' });
const { mutate: addMutate, isLoading: isAdding } = useAddKeywords(() => closeModal(false));
const deviceTabStyle = 'cursor-pointer px-3 py-2 rounded mr-2';

const existingTags: string[] = useMemo(() => {
const allTags = keywords.reduce((acc: string[], keyword) => [...acc, ...keyword.tags], []).filter((t) => t && t.trim() !== '');
return [...new Set(allTags)];
}, [keywords]);

const addKeywords = () => {
if (newKeywordsData.keywords) {
Expand All @@ -50,6 +56,8 @@ const AddKeywords = ({ closeModal, domain, keywords, scraperName = '', allowsCit
}
};

const deviceTabStyle = 'cursor-pointer px-3 py-2 rounded mr-2';

return (
<Modal closeModal={() => { closeModal(false); }} title={'Add New Keywords'} width="[420px]">
<div data-testid="addkeywords_modal">
Expand Down Expand Up @@ -91,14 +99,37 @@ const AddKeywords = ({ closeModal, domain, keywords, scraperName = '', allowsCit
</ul>
</div>
<div className='relative'>
{/* TODO: Insert Existing Tags as Suggestions */}
<input
className='w-full border rounded border-gray-200 py-2 px-4 pl-8 outline-none focus:border-indigo-300'
className='w-full border rounded border-gray-200 py-2 px-4 pl-12 outline-none focus:border-indigo-300'
placeholder='Insert Tags (Optional)'
value={newKeywordsData.tags}
onChange={(e) => setNewKeywordsData({ ...newKeywordsData, tags: e.target.value })}
/>
<span className='absolute text-gray-400 top-2 left-2'><Icon type="tags" size={16} /></span>
<span className='absolute text-gray-400 top-3 left-2 cursor-pointer' onClick={() => setShowTagSuggestions(!showTagSuggestions)}>
<Icon type="tags" size={16} color={showTagSuggestions ? '#777' : '#aaa'} />
<Icon type={showTagSuggestions ? 'caret-up' : 'caret-down'} size={14} color={showTagSuggestions ? '#666' : '#aaa'} />
</span>
{showTagSuggestions && (
<ul className={`absolute z-50
bg-white border border-t-0 border-gray-200 rounded rounded-t-none w-full`}>
{existingTags.length > 0 && existingTags.map((tag, index) => {
return newKeywordsData.tags.split(',').map((t) => t.trim()).includes(tag) === false && <li
className=' p-2 cursor-pointer hover:text-indigo-600 hover:bg-indigo-50 transition'
key={index}
onClick={() => {
const tagInput = newKeywordsData.tags;
// eslint-disable-next-line no-nested-ternary
const tagToInsert = tagInput + (tagInput.trim().slice(-1) === ',' ? '' : (tagInput.trim() ? ', ' : '')) + tag;
setNewKeywordsData({ ...newKeywordsData, tags: tagToInsert });
setShowTagSuggestions(false);
if (inputRef?.current) (inputRef.current as HTMLInputElement).focus();
}}>
<Icon type='tags' size={14} color='#bbb' /> {tag}
</li>;
})}
{existingTags.length === 0 && <p>No Existing Tags Found... </p>}
</ul>
)}
</div>
<div className='relative mt-2'>
<input
Expand Down
42 changes: 35 additions & 7 deletions components/keywords/AddTags.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { useState } from 'react';
import { useRef, useState } from 'react';
import { useUpdateKeywordTags } from '../../services/keywords';
import Icon from '../common/Icon';
import Modal from '../common/Modal';

type AddTagsProps = {
keywords: KeywordType[],
existingTags: string[],
closeModal: Function
}

const AddTags = ({ keywords = [], closeModal }: AddTagsProps) => {
const [tagInput, setTagInput] = useState('');
const AddTags = ({ keywords = [], existingTags = [], closeModal }: AddTagsProps) => {
const [tagInput, setTagInput] = useState(() => (keywords.length === 1 ? keywords[0].tags.join(', ') : ''));
const [inputError, setInputError] = useState('');
const [showSuggestions, setShowSuggestions] = useState(false);
const { mutate: updateMutate } = useUpdateKeywordTags(() => { setTagInput(''); });
const inputRef = useRef(null);

const addTag = () => {
if (keywords.length === 0) { return; }
if (!tagInput) {
if (!tagInput && keywords.length > 1) {
setInputError('Please Insert a Tag!');
setTimeout(() => { setInputError(''); }, 3000);
return;
Expand All @@ -24,7 +27,7 @@ const AddTags = ({ keywords = [], closeModal }: AddTagsProps) => {
const tagsArray = tagInput.split(',').map((t) => t.trim());
const tagsPayload:any = {};
keywords.forEach((keyword:KeywordType) => {
tagsPayload[keyword.ID] = [...keyword.tags, ...tagsArray];
tagsPayload[keyword.ID] = keywords.length === 1 ? tagsArray : [...(new Set([...keyword.tags, ...tagsArray]))];
});
updateMutate({ tags: tagsPayload });
};
Expand All @@ -33,9 +36,13 @@ const AddTags = ({ keywords = [], closeModal }: AddTagsProps) => {
<Modal closeModal={() => { closeModal(false); }} title={`Add New Tags to ${keywords.length} Selected Keyword`}>
<div className="relative">
{inputError && <span className="absolute top-[-24px] text-red-400 text-sm font-semibold">{inputError}</span>}
<span className='absolute text-gray-400 top-3 left-2'><Icon type="tags" size={16} /></span>
<span className='absolute text-gray-400 top-3 left-2 cursor-pointer' onClick={() => setShowSuggestions(!showSuggestions)}>
<Icon type="tags" size={16} color={showSuggestions ? '#777' : '#aaa'} />
<Icon type={showSuggestions ? 'caret-up' : 'caret-down'} size={14} color={showSuggestions ? '#666' : '#aaa'} />
</span>
<input
className='w-full border rounded border-gray-200 py-3 px-4 pl-8 outline-none focus:border-indigo-300'
ref={inputRef}
className='w-full border rounded border-gray-200 py-3 px-4 pl-12 outline-none focus:border-indigo-300'
placeholder='Insert Tags. eg: tag1, tag2'
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
Expand All @@ -46,6 +53,27 @@ const AddTags = ({ keywords = [], closeModal }: AddTagsProps) => {
}
}}
/>
{showSuggestions && (
<ul className={`absolute z-50
bg-white border border-t-0 border-gray-200 rounded rounded-t-none w-full`}>
{existingTags.length > 0 && existingTags.map((tag, index) => {
return tagInput.split(',').map((t) => t.trim()).includes(tag) === false && <li
className=' p-2 cursor-pointer hover:text-indigo-600 hover:bg-indigo-50 transition'
key={index}
onClick={() => {
// eslint-disable-next-line no-nested-ternary
const tagToInsert = tagInput + (tagInput.trim().slice(-1) === ',' ? '' : (tagInput.trim() ? ', ' : '')) + tag;
setTagInput(tagToInsert);
setShowSuggestions(false);
if (inputRef?.current) (inputRef.current as HTMLInputElement).focus();
}}>
<Icon type='tags' size={14} color='#bbb' /> {tag}
</li>;
})}
{existingTags.length === 0 && <p>No Existing Tags Found... </p>}
</ul>
)}

<button
className=" absolute right-2 top-2 cursor-pointer rounded p-2 px-4 bg-indigo-600 text-white font-semibold text-sm"
onClick={addTag}>
Expand Down
3 changes: 2 additions & 1 deletion components/keywords/KeywordTagManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type keywordTagManagerProps = {
allTags: string[]
}

const KeywordTagManager = ({ keyword, closeModal }: keywordTagManagerProps) => {
const KeywordTagManager = ({ keyword, allTags = [], closeModal }: keywordTagManagerProps) => {
const [showAddTag, setShowAddTag] = useState<boolean>(false);
const { mutate: updateMutate } = useUpdateKeywordTags(() => { });

Expand Down Expand Up @@ -51,6 +51,7 @@ const KeywordTagManager = ({ keyword, closeModal }: keywordTagManagerProps) => {
</div>
{showAddTag && keyword && (
<AddTags
existingTags={allTags}
keywords={[keyword]}
closeModal={() => setShowAddTag(false)}
/>
Expand Down
3 changes: 2 additions & 1 deletion components/keywords/KeywordsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const KeywordsTable = (props: KeywordsTableProps) => {
}, [keywords, device, sortBy, filterParams, scDataType]);

const allDomainTags: string[] = useMemo(() => {
const allTags = keywords.reduce((acc: string[], keyword) => [...acc, ...keyword.tags], []);
const allTags = keywords.reduce((acc: string[], keyword) => [...acc, ...keyword.tags], []).filter((t) => t && t.trim() !== '');
return [...new Set(allTags)];
}, [keywords]);

Expand Down Expand Up @@ -251,6 +251,7 @@ const KeywordsTable = (props: KeywordsTableProps) => {
)}
{showAddTags && (
<AddTags
existingTags={allDomainTags}
keywords={keywords.filter((k) => selectedKeywords.includes(k.ID))}
closeModal={() => setShowAddTags(false)}
/>
Expand Down

0 comments on commit 407ab8d

Please sign in to comment.