diff --git a/app/components/entry/EntryInput/index.tsx b/app/components/entry/EntryInput/index.tsx index 90d07459e3..48bc774b2b 100644 --- a/app/components/entry/EntryInput/index.tsx +++ b/app/components/entry/EntryInput/index.tsx @@ -250,6 +250,7 @@ function EntryInput(props: EntryInputProp styles.section, sectionContainerClassName, compactMode && styles.compact, + displayHorizontally && styles.horizontal, )} renderer={CompactSection} keySelector={sectionKeySelector} diff --git a/app/components/entry/EntryInput/styles.css b/app/components/entry/EntryInput/styles.css index dd68ab066b..86d0ff405c 100644 --- a/app/components/entry/EntryInput/styles.css +++ b/app/components/entry/EntryInput/styles.css @@ -69,6 +69,9 @@ &.compact { border-bottom: var(--dui-width-separator-thin) solid var(--dui-color-separator); } + &.horizontal { + border-bottom: unset; + } } .secondary-tagging { diff --git a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx index 1aeb173ba0..7aeab3323a 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx @@ -4,16 +4,11 @@ import { Kraken, Container, Message, - QuickActionButton, } from '@the-deep/deep-ui'; import { SetValueArg, Error, } from '@togglecorp/toggle-form'; -import { - IoClose, -} from 'react-icons/io5'; -import { FiEdit2 } from 'react-icons/fi'; import EntryInput from '#components/entry/EntryInput'; import { GeoArea } from '#components/GeoMultiSelectInput'; @@ -34,12 +29,9 @@ interface Props { value: PartialEntryType; onChange: (val: SetValueArg, name: NAME) => void; error: Error | undefined; - onEntryCreateButtonClick: () => void; variant?: 'normal' | 'compact' | 'nlp'; // NOTE: Normal entry creation refers to entry created without use of // recommendations - onNormalEntryCreateButtonClick: () => void; - onEntryDiscardButtonClick: () => void; geoAreaOptions: GeoArea[] | undefined | null; onGeoAreaOptionsChange: React.Dispatch>; predictionsLoading?: boolean; @@ -50,6 +42,8 @@ interface Props { messageText: string | undefined; excerptShown?: boolean; displayHorizontally?: boolean; + + footerActions: React.ReactNode; } function AssistPopup(props: Props) { @@ -63,9 +57,6 @@ function AssistPopup(props: Props(props: Props { @@ -99,28 +91,7 @@ function AssistPopup(props: Props - - - - - - - - )} + footerQuickActions={footerActions} contentClassName={styles.body} > {isMessageShown ? ( diff --git a/app/views/EntryEdit/LeftPane/AssistItem/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/index.tsx index 865542478a..af2cbfa8b4 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/index.tsx @@ -18,6 +18,7 @@ import { Container, } from '@the-deep/deep-ui'; import { IoClose } from 'react-icons/io5'; +import { FiEdit2 } from 'react-icons/fi'; import { GeoArea } from '#components/GeoMultiSelectInput'; import brainIcon from '#resources/img/brain.svg'; @@ -152,7 +153,11 @@ interface Props { className?: string; text: string; onAssistedEntryAdd: ( - (newEntry: EntryInput, locations?: GeoArea[]) => void + ( + newEntry: EntryInput, + locations?: GeoArea[], + selectCreatedEntry?: boolean, + ) => void ) | undefined; frameworkDetails?: Framework; leadId: string; @@ -641,6 +646,7 @@ function AssistItem(props: Props) { draftEntry: data?.project?.assistedTagging?.draftEntry?.id, }, geoAreaOptions ?? undefined, + true, ); } }, @@ -746,9 +752,6 @@ function AssistItem(props: Props) { leadId={leadId} hints={allHints} recommendations={allRecommendations} - onEntryDiscardButtonClick={handleDiscardButtonClick} - onEntryCreateButtonClick={handleEntryCreateButtonClick} - onNormalEntryCreateButtonClick={handleNormalEntryCreateButtonClick} geoAreaOptions={geoAreaOptions} onGeoAreaOptionsChange={setGeoAreaOptions} predictionsLoading={ @@ -758,6 +761,34 @@ function AssistItem(props: Props) { } predictionsErrored={!!fetchErrors || !!createErrors || isErrored} messageText={messageText} + footerActions={( + <> + + + + + + + + )} /> )} diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index 1e3db429da..137fea22b3 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -23,6 +23,10 @@ import { import { Modal, ListView, + Tab, + Tabs, + TabPanel, + TabList, useAlert, Button, } from '@the-deep/deep-ui'; @@ -54,12 +58,11 @@ import { CreateAutoDraftEntriesMutationVariables, AutoDraftEntriesStatusQuery, AutoDraftEntriesStatusQueryVariables, + DiscardDraftEntryMutation, + DiscardDraftEntryMutationVariables, } from '#generated/types'; import AssistPopup from '../AssistItem/AssistPopup'; import { createDefaultAttributes } from '../../utils'; - -import styles from './styles.css'; - import { createOrganigramAttr, createMatrix1dAttr, @@ -70,20 +73,30 @@ import { createGeoAttr, } from '../AssistItem/utils'; +import styles from './styles.css'; + const GEOLOCATION_DEEPL_MODEL_ID = 'geolocation'; const AUTO_ENTRIES_FOR_LEAD = gql` query AutoEntriesForLead( $projectId: ID!, - $leadIds: [ID!], + $leadId: ID!, + $isDiscarded: Boolean, ) { project(id: $projectId) { id + lead(id: $leadId) { + draftEntryStat { + discardedDraftEnrty + undiscardedDraftEntry + } + } assistedTagging { draftEntryByLeads( filter: { - draftEntryType: AUTO, - lead: $leadIds, + draftEntryTypes: AUTO, + leads: $leadId, + isDiscarded: $isDiscarded, }) { id excerpt @@ -142,6 +155,27 @@ const AUTO_DRAFT_ENTRIES_STATUS = gql` } `; +const DISCARD_DRAFT_ENTRY = gql` + mutation DiscardDraftEntry( + $projectId: ID!, + $draftEntryId: ID!, + $input: UpdateDraftEntryInputType!, + ){ + project(id: $projectId) { + id + assistedTagging { + discardDraftEntry( + data: $input, + id: $draftEntryId, + ) { + errors + ok + } + } + } + } +`; + interface EntryAttributes { predictions: { tags: string[]; @@ -328,6 +362,8 @@ function handleMappingsFetch(entryAttributes: EntryAttributes) { const entryKeySelector = (entry: PartialEntryType) => entry.clientId; +type EntriesTabType = 'extracted' | 'discarded'; + interface Props { onModalClose: () => void; projectId: string; @@ -358,6 +394,11 @@ function AutoEntriesModal(props: Props) { ) ), [createdEntries]); + const [ + selectedTab, + setSelectedTab, + ] = useState('extracted'); + const { allWidgets, filteredWidgets, @@ -528,14 +569,18 @@ function AutoEntriesModal(props: Props) { const autoEntriesVariables = useMemo(() => ({ projectId, - leadIds: [leadId], + leadId, + isDiscarded: selectedTab === 'discarded', }), [ projectId, leadId, + selectedTab, ]); const { + data: autoEntries, loading: autoEntriesLoading, + refetch: retriggerAutoEntriesFetch, } = useQuery( AUTO_ENTRIES_FOR_LEAD, { @@ -543,6 +588,8 @@ function AutoEntriesModal(props: Props) { || extractionStatus !== 'SUCCESS' || isNotDefined(autoEntriesVariables), variables: autoEntriesVariables, + // TODO: This is due to caching issue in apollo. + notifyOnNetworkStatusChange: true, onCompleted: (response) => { const entries = response.project?.assistedTagging?.draftEntryByLeads; const transformedEntries = (entries ?? [])?.map((entry) => { @@ -629,6 +676,11 @@ function AutoEntriesModal(props: Props) { }, ); + const discardedEntriesCount = autoEntries?.project?.lead + ?.draftEntryStat?.discardedDraftEnrty; + const undiscardedEntriesCount = autoEntries?.project?.lead + ?.draftEntryStat?.undiscardedDraftEntry; + const handleEntryCreateButtonClick = useCallback((entryId: string) => { if (!allRecommendations?.[entryId]) { return; @@ -636,6 +688,20 @@ function AutoEntriesModal(props: Props) { const selectedEntry = value?.entries?.find((item) => item.clientId === entryId); if (onAssistedEntryAdd && selectedEntry) { + const duplicateEntryCheck = createdEntries?.find( + (entry) => entry.droppedExcerpt === selectedEntry.droppedExcerpt, + ); + + if (isDefined(duplicateEntryCheck)) { + alert.show( + 'Similar entry found. Failed to add entry from recommendations.', + { + variant: 'error', + }, + ); + return; + } + const defaultAttributes = createDefaultAttributes(allWidgets); const newAttributes = mergeLists( @@ -680,6 +746,79 @@ function AutoEntriesModal(props: Props) { geoAreaOptions, allRecommendations, onAssistedEntryAdd, + createdEntries, + ]); + + const [ + triggerDiscardDraftEntry, + ] = useMutation( + DISCARD_DRAFT_ENTRY, + { + onCompleted: (response) => { + const discardDraftEntryResponse = response?.project?.assistedTagging + ?.discardDraftEntry; + retriggerAutoEntriesFetch(); + if (discardDraftEntryResponse?.ok) { + alert.show( + 'Successfully changed the discard status.', + { + variant: 'success', + }, + ); + } else { + alert.show( + 'Failed to change the discard status.', + { + variant: 'error', + }, + ); + } + }, + onError: () => { + alert.show( + 'Failed to change the discard status.', + { + variant: 'error', + }, + ); + }, + }, + ); + + const handleDiscardDraftEntryClick = useCallback((entryId: string | undefined) => { + triggerDiscardDraftEntry({ + variables: { + projectId, + input: { + lead: leadId, + isDiscarded: true, + }, + // FIXME: Handle this better + draftEntryId: entryId ?? '', + }, + }); + }, [ + triggerDiscardDraftEntry, + leadId, + projectId, + ]); + + const handleUndiscardEntryClick = useCallback((entryId: string | undefined) => { + triggerDiscardDraftEntry({ + variables: { + projectId, + input: { + lead: leadId, + isDiscarded: false, + }, + // FIXME: Handle this better + draftEntryId: entryId ?? '', + }, + }); + }, [ + triggerDiscardDraftEntry, + leadId, + projectId, ]); const filteredEntries = useMemo(() => ( @@ -697,6 +836,36 @@ function AutoEntriesModal(props: Props) { ) => { const onEntryCreateButtonClick = () => handleEntryCreateButtonClick(entryId); const index = value?.entries?.findIndex((item) => item.clientId === entryId); + const footerActions = (selectedTab === 'extracted' ? ( +
+ + +
+ ) : ( + + )); return ({ frameworkDetails, @@ -709,17 +878,15 @@ function AutoEntriesModal(props: Props) { hints: allHints?.[entryId], recommendations: allRecommendations?.[entryId], geoAreaOptions: geoAreaOptions?.[entryId], - onEntryDiscardButtonClick: noOp, - onEntryCreateButtonClick, - onNormalEntryCreateButtonClick: noOp, onGeoAreaOptionsChange: noOp, predictionsLoading: false, predictionsErrored: false, messageText: undefined, - variant: 'nlp' as const, + variant: 'normal' as const, error: undefined, excerptShown: true, displayHorizontally: true, + footerActions, }); }, [ value?.entries, @@ -730,6 +897,9 @@ function AutoEntriesModal(props: Props) { frameworkDetails, leadId, geoAreaOptions, + handleDiscardDraftEntryClick, + handleUndiscardEntryClick, + selectedTab, ]); const isFiltered = useMemo(() => ( @@ -739,45 +909,101 @@ function AutoEntriesModal(props: Props) { value?.entries, ]); + /* const hideList = draftEntriesLoading || autoEntriesLoading || extractionStatus === 'NONE'; + */ return ( - + + + {`All Recommendations (${undiscardedEntriesCount})`} + + - Recommend entries - - )} - messageShown - messageIconShown - borderBetweenItem - /> + {`Discarded Recommendations (${discardedEntriesCount})`} + + + + + Recommend entries + + )} + messageShown + messageIconShown + borderBetweenItem + /> + + + + Recommend entries + + )} + messageShown + messageIconShown + borderBetweenItem + /> + + ); } diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css index 065f48366c..1abbc56c75 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css @@ -4,4 +4,9 @@ height: 360px; } } + + .footer-buttons { + display: flex; + gap: var(--dui-spacing-medium); + } } diff --git a/app/views/EntryEdit/index.tsx b/app/views/EntryEdit/index.tsx index 8f4f081c3d..2775b54b0e 100644 --- a/app/views/EntryEdit/index.tsx +++ b/app/views/EntryEdit/index.tsx @@ -907,8 +907,14 @@ function EntryEdit(props: Props) { ); const handleAssistedEntryAdd = useCallback( - (newValue: PartialEntryType, newGeoAreaOptions?: GeoArea[]) => { - createRestorePoint(); + ( + newValue: PartialEntryType, + newGeoAreaOptions?: GeoArea[], + selectCreatedEntry = false, + ) => { + if (selectCreatedEntry) { + createRestorePoint(); + } setFormFieldValue( (prevValue: PartialFormType['entries']) => [ ...(prevValue ?? []), @@ -919,7 +925,9 @@ function EntryEdit(props: Props) { ], 'entries', ); - setSelectedEntry(newValue.clientId); + if (selectCreatedEntry) { + setSelectedEntry(newValue.clientId); + } if (newGeoAreaOptions && newGeoAreaOptions.length > 0) { setGeoAreaOptions((oldAreas) => { const newAreas = unique([ @@ -936,6 +944,8 @@ function EntryEdit(props: Props) { ], ); + console.log('selected', selectedEntry); + const handleEntryChangeApprove = useCallback( () => { clearRestorePoint();