All files / src/pages/main-page/project-folders model.ts

90% Statements 27/30
71.42% Branches 10/14
100% Functions 2/2
91.66% Lines 22/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139103x 103x                                                                                                 585x 31x   31x 31x         775x                       190x                             190x     22x     190x                   585x     585x   31x 31x 31x           35x 35x 35x 27x 27x     31x                                
export const ROOT_PROJECT_FOLDER_ID = null;
export const PROJECT_CARD_DRAG_TYPE = "project-card";
 
export type ProjectFolderId = number | null;
export type ProjectViewMode = "grid" | "list";
 
export type ProjectFolderPathItem = {
  id: number;
  name: string;
  parentId: ProjectFolderId;
};
 
export type ProjectFolderItem = ProjectFolderPathItem & {
  projectCount: number;
};
 
export type ProjectFolderIndex = {
  foldersById: ReadonlyMap<number, ProjectFolderPathItem>;
};
 
export type ProjectCardDragItem = {
  projectId: number;
  projectName: string;
  folderId: ProjectFolderId;
};
 
export type MoveProject = (
  projectId: number,
  folderId: ProjectFolderId,
) => Promise<void> | void;
 
export type FolderBreadcrumb = {
  id: ProjectFolderId;
  name: string;
};
 
type ProjectFolderRecord = {
  id: number;
  name: string;
  parent: number | null;
  project_count: number;
};
 
type ProjectFolderNavigationRecord = Omit<
  ProjectFolderRecord,
  "project_count" | "parent"
> & { parent?: number | null };
 
/** Parse the folder query parameter as a database identity. */
export function parseProjectFolderId(value: string | null) {
  if (value === null) return ROOT_PROJECT_FOLDER_ID;
  Iif (!/^[1-9]\d*$/.test(value)) return undefined;
 
  const id = Number(value);
  return Number.isSafeInteger(id) ? id : undefined;
}
 
/** Adapt the generated API resource shape for folder UI components. */
export function buildProjectFolderItems(folders: ProjectFolderRecord[]) {
  return folders.map<ProjectFolderItem>((folder) => ({
    id: folder.id,
    name: folder.name,
    parentId: folder.parent ?? ROOT_PROJECT_FOLDER_ID,
    projectCount: folder.project_count,
  }));
}
 
/** Adapt breadcrumb resources without inventing subtree project counts. */
export function buildProjectFolderPathItems(
  folders: ProjectFolderNavigationRecord[],
) {
  return folders.map<ProjectFolderPathItem>((folder) => ({
    id: folder.id,
    name: folder.name,
    parentId: folder.parent ?? ROOT_PROJECT_FOLDER_ID,
  }));
}
 
/**
 * Index one bounded folder workspace response for path navigation.
 *
 * Keeping the identity map at the workspace boundary avoids repeated path scans.
 */
export function buildProjectFolderIndex(
  folders: ProjectFolderPathItem[],
): ProjectFolderIndex {
  const foldersById = new Map<number, ProjectFolderPathItem>();
 
  for (const folder of folders) {
    foldersById.set(folder.id, folder);
  }
 
  return {
    foldersById,
  };
}
 
/** Build breadcrumbs by following stable parent relationships to the root. */
export function getProjectFolderBreadcrumbs(
  folderIndex: ProjectFolderIndex,
  currentFolderId: ProjectFolderId,
) {
  const breadcrumbs: FolderBreadcrumb[] = [
    { id: ROOT_PROJECT_FOLDER_ID, name: "My Projects" },
  ];
  if (currentFolderId === ROOT_PROJECT_FOLDER_ID) return breadcrumbs;
 
  const ancestors: FolderBreadcrumb[] = [];
  const visited = new Set<number>();
  let nextFolderId: ProjectFolderId = currentFolderId;
 
  while (
    nextFolderId !== ROOT_PROJECT_FOLDER_ID &&
    !visited.has(nextFolderId)
  ) {
    visited.add(nextFolderId);
    const folder = folderIndex.foldersById.get(nextFolderId);
    if (!folder) break;
    ancestors.push({ id: folder.id, name: folder.name });
    nextFolderId = folder.parentId;
  }
 
  return [...breadcrumbs, ...ancestors.reverse()];
}
 
/** Return the parent relationship for Back navigation. */
export function getParentProjectFolderId(
  folderIndex: ProjectFolderIndex,
  currentFolderId: ProjectFolderId,
) {
  if (currentFolderId === ROOT_PROJECT_FOLDER_ID) {
    return ROOT_PROJECT_FOLDER_ID;
  }
  return (
    folderIndex.foldersById.get(currentFolderId)?.parentId ??
    ROOT_PROJECT_FOLDER_ID
  );
}