diff --git a/src/components/ui/CopilotSelector.tsx b/src/components/ui/CopilotSelector.tsx index d49623b..6101d6e 100644 --- a/src/components/ui/CopilotSelector.tsx +++ b/src/components/ui/CopilotSelector.tsx @@ -1,12 +1,12 @@ import { UserCompanySelector } from 'copilot-design-system' -import { useUserChannel } from '@/features/sync/hooks/useUserChannel' -import type { SelectorValue } from '@/features/sync/types' +import type { SelectorClientsCompanies, SelectorValue } from '@/features/sync/types' import type { UserCompanySelectorInputValue } from '@/lib/copilot/types' type CopilotSelectorProps = { name: string initialValue?: SelectorValue[] onChange: (val: UserCompanySelectorInputValue[]) => void + options: SelectorClientsCompanies disabled?: boolean } @@ -15,9 +15,8 @@ export const CopilotSelector = ({ initialValue, onChange, disabled, + options, }: CopilotSelectorProps) => { - const { userChannelList } = useUserChannel() - if (typeof window !== 'undefined') return (
@@ -25,8 +24,8 @@ export const CopilotSelector = ({ name={name} initialValue={initialValue} placeholder="Select File Channel" - clientUsers={userChannelList.clients ?? []} - companies={userChannelList.companies ?? []} + clientUsers={options.clients ?? []} + companies={options.companies ?? []} grouped={true} limitSelectedOptions={1} onChange={onChange} diff --git a/src/features/dropbox/lib/Dropbox.service.ts b/src/features/dropbox/lib/Dropbox.service.ts index 1f00d5a..68dff2b 100644 --- a/src/features/dropbox/lib/Dropbox.service.ts +++ b/src/features/dropbox/lib/Dropbox.service.ts @@ -4,6 +4,7 @@ import type { NextRequest } from 'next/server' import { MAX_FETCH_DBX_RESOURCES, MAX_FETCH_DBX_SEARCH_LIMIT } from '@/constants/limits' import { ObjectType } from '@/db/constants' import APIError from '@/errors/APIError' +import { MapFilesService } from '@/features/sync/lib/MapFiles.service' import type { Folder } from '@/features/sync/types' import AuthenticatedDropboxService from '@/lib/dropbox/AuthenticatedDropbox.service' import logger from '@/lib/logger' @@ -30,7 +31,7 @@ export class DropboxService extends AuthenticatedDropboxService { throw new APIError('Cannot fetch the folders', searchResponse.status) } - return this.formatSearchResults(searchResponse.result.matches) + return await this.formatSearchResults(searchResponse.result.matches) } // Now this call will be rooted in the Team Space @@ -47,11 +48,15 @@ export class DropboxService extends AuthenticatedDropboxService { } logger.info('DropboxService#getFolderTree :: Fetched folder tree', entries) - return this.buildFolderTree(entries) + return await this.buildFolderTree(entries) } - private buildFolderTree(entries: files.ListFolderResult['entries']): Folder[] { + private async buildFolderTree(entries: files.ListFolderResult['entries']): Promise { const root: Folder[] = [] + const mapService = new MapFilesService(this.user, this.connectionToken) + const mapList = (await mapService.getAllChannelMaps()).map( + (channelMap) => channelMap.dbxRootPath, + ) const findOrCreate = (children: Folder[], path: string, label: string) => { let node = children.find((c) => c.path === path) @@ -65,7 +70,10 @@ export class DropboxService extends AuthenticatedDropboxService { logger.info('DropboxService#buildFolderTree :: Building folder tree', entries) for (const item of entries) { if (item['.tag'] === ObjectType.FOLDER) { - const parts = item.path_display?.split('/').filter(Boolean) + // below condition is to make sure the map is one-to-one + if (!item.path_display || mapList.includes(item.path_display)) continue + + const parts = item.path_display.split('/').filter(Boolean) let currentChildren: Folder[] = root let currentPath = '' @@ -84,13 +92,22 @@ export class DropboxService extends AuthenticatedDropboxService { return root } - private formatSearchResults(matches: files.SearchMatchV2[]) { + private async formatSearchResults(matches: files.SearchMatchV2[]) { + const mapService = new MapFilesService(this.user, this.connectionToken) + const mapList = (await mapService.getAllChannelMaps()).map( + (channelMap) => channelMap.dbxRootPath, + ) + return matches .map((match) => { if (match.metadata['.tag'] === 'other') return null const data = match.metadata.metadata if (data['.tag'] !== ObjectType.FOLDER) return null + + // below condition is to make sure the map is one-to-one + if (!data.path_display || mapList.includes(data.path_display)) return null + return { path: data.path_display, label: data.path_display, diff --git a/src/features/sync/components/Table.tsx b/src/features/sync/components/Table.tsx index 948cad4..bdab10f 100644 --- a/src/features/sync/components/Table.tsx +++ b/src/features/sync/components/Table.tsx @@ -8,7 +8,7 @@ import { cn } from '@/components/utils' import LastSyncAt from '@/features/sync/components/LastSyncedAt' import { getCompanySelectorValue } from '@/features/sync/helper/sync.helper' import { useFolder } from '@/features/sync/hooks/useFolder' -import { useTable } from '@/features/sync/hooks/useTable' +import { useTable, useUpdateUserList } from '@/features/sync/hooks/useTable' import { useUserChannel } from '@/features/sync/hooks/useUserChannel' import { generateRandomString } from '@/utils/random' @@ -55,8 +55,9 @@ const MappingTableRow = () => { filteredValue, onDropboxFolderChange, } = useTable() - const { tempMapList, userChannelList, syncedPercentage } = useUserChannel() - const { folderTree, isFolderTreeLoading } = useFolder() + const { unselectedChannelList } = useUpdateUserList() + const { isFolderTreeLoading } = useFolder() + const { tempMapList, userChannelList, syncedPercentage, tempFolders } = useUserChannel() if (isFolderTreeLoading) { return ( @@ -82,13 +83,14 @@ const MappingTableRow = () => { )} onChange={(val) => onUserSelectorValueChange(val, index)} disabled={!!mapItem.status} + options={unselectedChannelList || {}} /> onDropboxFolderChange(val, index)} - options={folderTree} + options={tempFolders || []} placeholder="Search Dropbox folder" disabled={!!mapItem.status} /> diff --git a/src/features/sync/context/UserChannelContext.tsx b/src/features/sync/context/UserChannelContext.tsx index bb1ec8d..bd2029a 100644 --- a/src/features/sync/context/UserChannelContext.tsx +++ b/src/features/sync/context/UserChannelContext.tsx @@ -1,13 +1,15 @@ 'use client' import { createContext, type ReactNode, useState } from 'react' -import type { MapList, SelectorClientsCompanies } from '@/features/sync/types' +import type { Folder, MapList, SelectorClientsCompanies } from '@/features/sync/types' export type UserChannelContextType = { userChannelList: SelectorClientsCompanies mapList: MapList[] tempMapList: MapList[] syncedPercentage?: { [key: string]: number } + folders?: Folder[] + tempFolders?: Folder[] } export const UserChannelContext = createContext< @@ -29,6 +31,8 @@ export const UserChannelContextProvider = ({ mapList, tempMapList, syncedPercentage: {}, + folders: [], + tempFolders: [], }) return ( diff --git a/src/features/sync/hooks/useFolder.ts b/src/features/sync/hooks/useFolder.ts index efe64ba..97b52a7 100644 --- a/src/features/sync/hooks/useFolder.ts +++ b/src/features/sync/hooks/useFolder.ts @@ -1,10 +1,10 @@ import { useCallback, useEffect, useState } from 'react' import { useAuthContext } from '@/features/auth/hooks/useAuth' -import type { Folder } from '@/features/sync/types' +import { useUserChannel } from '@/features/sync/hooks/useUserChannel' export const useFolder = () => { const { user, connectionStatus } = useAuthContext() - const [folderTree, setFolderTree] = useState([]) + const { setUserChannel } = useUserChannel() const [isFolderTreeLoading, setIsFolderTreeLoading] = useState(true) const getPathOptions = useCallback(async () => { @@ -21,14 +21,14 @@ export const useFolder = () => { }, }) const resp = await response.json() - setFolderTree(resp.folders) + setUserChannel((prev) => ({ ...prev, folders: resp.folders, tempFolders: resp.folders })) setIsFolderTreeLoading(false) - }, [user.token, connectionStatus]) + }, [user.token, connectionStatus, setUserChannel]) useEffect(() => { // biome-ignore lint/nursery/noFloatingPromises: floating promises are fine here getPathOptions() }, [getPathOptions]) - return { folderTree, isFolderTreeLoading } + return { isFolderTreeLoading } } diff --git a/src/features/sync/hooks/useSubHeader.ts b/src/features/sync/hooks/useSubHeader.ts index 249c8a0..240ee85 100644 --- a/src/features/sync/hooks/useSubHeader.ts +++ b/src/features/sync/hooks/useSubHeader.ts @@ -1,7 +1,8 @@ import { useUserChannel } from '@/features/sync/hooks/useUserChannel' +import { getFreshFolders } from '@/helper/table.helper' export const useSubHeader = () => { - const { setUserChannel, tempMapList } = useUserChannel() + const { setUserChannel, tempMapList, folders } = useUserChannel() const handleAddRule = () => { const lastMap = tempMapList?.[tempMapList.length - 1] @@ -24,6 +25,7 @@ export const useSubHeader = () => { lastSyncedAt: null, }, ], + tempFolders: getFreshFolders(tempMapList, folders), })) } } diff --git a/src/features/sync/hooks/useTable.ts b/src/features/sync/hooks/useTable.ts index 96a9e31..0698628 100644 --- a/src/features/sync/hooks/useTable.ts +++ b/src/features/sync/hooks/useTable.ts @@ -1,13 +1,14 @@ -import { useState } from 'react' +import { useCallback, useState } from 'react' import { useAuthContext } from '@/features/auth/hooks/useAuth' import { useUserChannel } from '@/features/sync/hooks/useUserChannel' import type { MapList } from '@/features/sync/types' import { postFetcher } from '@/helper/fetcher.helper' +import { getFreshFolders } from '@/helper/table.helper' import { type UserCompanySelectorInputValue, UserCompanySelectorObject } from '@/lib/copilot/types' export const useTable = () => { const { user } = useAuthContext() - const { tempMapList, setUserChannel, userChannelList } = useUserChannel() + const { tempMapList, setUserChannel, userChannelList, folders } = useUserChannel() const [fileChannelIds, setFileChannelIds] = useState<{ [key: number]: string }>() @@ -58,6 +59,10 @@ export const useTable = () => { setUserChannel((prev) => ({ ...prev, tempMapList: prev.tempMapList.filter((_, i) => i !== index), + tempFolders: getFreshFolders( + prev.tempMapList.filter((_, i) => i !== index), + folders, + ), })) setFilteredValue((prev) => ({ ...prev, [index]: null })) } @@ -115,3 +120,22 @@ export const useTable = () => { handleSyncStatusChange, } } + +export const useUpdateUserList = () => { + const { userChannelList, tempMapList } = useUserChannel() + const selectedChannelIds = tempMapList.map((map) => map.fileChannelId) + + const getNewChannelList = useCallback(() => { + const newClientList = userChannelList.clients?.filter( + (client) => client.fileChannelId && !selectedChannelIds.includes(client.fileChannelId), + ) + const newCompanyList = userChannelList.companies?.filter( + (company) => company.fileChannelId && !selectedChannelIds.includes(company.fileChannelId), + ) + const newChannelList = { clients: newClientList, companies: newCompanyList } + + return newChannelList + }, [userChannelList, selectedChannelIds]) + + return { unselectedChannelList: getNewChannelList() } +} diff --git a/src/helper/table.helper.ts b/src/helper/table.helper.ts new file mode 100644 index 0000000..6d4ff08 --- /dev/null +++ b/src/helper/table.helper.ts @@ -0,0 +1,7 @@ +import type { Folder, MapList } from '@/features/sync/types' + +export function getFreshFolders(mapList: MapList[], folders?: Folder[]) { + if (!folders) return [] + const rootPathList = mapList.map((mapItem) => mapItem.dbxRootPath) + return folders.filter((folder) => !rootPathList.includes(folder.path)) +}