Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/components/ui/CopilotSelector.tsx
Original file line number Diff line number Diff line change
@@ -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
}

Expand All @@ -15,18 +15,17 @@ export const CopilotSelector = ({
initialValue,
onChange,
disabled,
options,
}: CopilotSelectorProps) => {
const { userChannelList } = useUserChannel()

if (typeof window !== 'undefined')
return (
<div className="w-64">
<UserCompanySelector
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}
Expand Down
27 changes: 22 additions & 5 deletions src/features/dropbox/lib/Dropbox.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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<Folder[]> {
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)
Expand All @@ -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 = ''

Expand All @@ -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,
Expand Down
10 changes: 6 additions & 4 deletions src/features/sync/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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 (
Expand All @@ -82,13 +83,14 @@ const MappingTableRow = () => {
)}
onChange={(val) => onUserSelectorValueChange(val, index)}
disabled={!!mapItem.status}
options={unselectedChannelList || {}}
/>
</td>
<td className="w-96 whitespace-nowrap px-6 py-2">
<TreeSelect
value={filteredValue?.[index] || mapItem.dbxRootPath}
onChange={(val) => onDropboxFolderChange(val, index)}
options={folderTree}
options={tempFolders || []}
placeholder="Search Dropbox folder"
disabled={!!mapItem.status}
/>
Expand Down
6 changes: 5 additions & 1 deletion src/features/sync/context/UserChannelContext.tsx
Original file line number Diff line number Diff line change
@@ -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<
Expand All @@ -29,6 +31,8 @@ export const UserChannelContextProvider = ({
mapList,
tempMapList,
syncedPercentage: {},
folders: [],
tempFolders: [],
})

return (
Expand Down
10 changes: 5 additions & 5 deletions src/features/sync/hooks/useFolder.ts
Original file line number Diff line number Diff line change
@@ -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<Folder[]>([])
const { setUserChannel } = useUserChannel()
const [isFolderTreeLoading, setIsFolderTreeLoading] = useState(true)

const getPathOptions = useCallback(async () => {
Expand All @@ -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 }
}
4 changes: 3 additions & 1 deletion src/features/sync/hooks/useSubHeader.ts
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -24,6 +25,7 @@ export const useSubHeader = () => {
lastSyncedAt: null,
},
],
tempFolders: getFreshFolders(tempMapList, folders),
}))
}
}
Expand Down
28 changes: 26 additions & 2 deletions src/features/sync/hooks/useTable.ts
Original file line number Diff line number Diff line change
@@ -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
}>()
Expand Down Expand Up @@ -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 }))
}
Expand Down Expand Up @@ -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() }
}
7 changes: 7 additions & 0 deletions src/helper/table.helper.ts
Original file line number Diff line number Diff line change
@@ -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))
}