<!-- #region RepositoryDashboardGrid [C:3] [TYPE Component] [SEMANTICS dashboard, grid, pagination, selection, repository] -->
<!-- @BRIEF Component component: components/RepositoryDashboardGrid.svelte -->
<!-- @LAYER UI -->
<!--
@SEMANTICS: dashboard, grid, selection, pagination
@PURPOSE:   Displays a grid of dashboards with selection and pagination.
@LAYER     Component
@RELATION  USED_BY -> [frontend/src/routes/migration/+page.svelte]

@INVARIANT: Selected IDs must be a subset of available dashboards.
-->

<script lang="ts">
  // [SECTION: IMPORTS]
  import { untrack } from "svelte";
  import type { DashboardMetadata } from "../types/dashboard";
  import { t } from '$lib/i18n/index.svelte.js';
  import { Button, Input } from "../lib/ui";
  import GitManager from "./git/GitManager.svelte";
  import { gitService } from "../services/gitService";
  import { addToast } from "$lib/toasts.svelte.js";
  // [/SECTION]

  // [SECTION: PROPS]
  let {
    dashboards = [],
    selectedIds = [],
    statusMode = "dashboard",
    envId = null,
    repositoriesOnly = false,
  } = $props();

  // [/SECTION]

  // [SECTION: STATE]
  let filterText = $state("");
  let currentPage = $state(0);
  let pageSize = $state(20);
  let sortColumn: keyof DashboardMetadata = $state("title");
  let sortDirection: "asc" | "desc" = $state("asc");
  // [/SECTION]

  // [SECTION: UI STATE]
  let showGitManager = $state(false);
  let gitDashboardId: string | null = $state(null);
  let gitDashboardTitle = $state("");
  let repositoryStatusByDashboardId = $state<Record<number, string>>({});
  let repositoryStatusRequestId = $state(0);
  let bulkActionRunning = $state(false);
  // [/SECTION]

  // [SECTION: DERIVED]
  let filteredDashboards = $derived(
    dashboards.filter((d) => {
      const matchesTitle = d.title.toLowerCase().includes(filterText.toLowerCase());
      if (!matchesTitle) return false;
      if (!repositoriesOnly || statusMode !== "repository") return true;
      return getRepositoryStatusToken(d.id) !== "no_repo";
    }),
  );

  let sortedDashboards = $derived(
    [...filteredDashboards].sort((a, b) => {
      let aVal =
        sortColumn === "status"
          ? getSortStatusValue(a)
          : a[sortColumn];
      let bVal =
        sortColumn === "status"
          ? getSortStatusValue(b)
          : b[sortColumn];
      if (sortColumn === "id") {
        aVal = Number(aVal);
        bVal = Number(bVal);
      }
      if (aVal < bVal) return sortDirection === "asc" ? -1 : 1;
      if (aVal > bVal) return sortDirection === "asc" ? 1 : -1;
      return 0;
    }),
  );

  let paginatedDashboards = $derived(
    sortedDashboards.slice(
      currentPage * pageSize,
      (currentPage + 1) * pageSize,
    ),
  );

  let totalPages = $derived(Math.ceil(sortedDashboards.length / pageSize));

  let allSelected = $derived(
    paginatedDashboards.length > 0 &&
      paginatedDashboards.every((d) => selectedIds.includes(d.id)),
  );
  let someSelected = $derived(
    paginatedDashboards.some((d) => selectedIds.includes(d.id)),
  );
  // [/SECTION]

  // #region handleSort:Function [TYPE Function]
  // @PURPOSE: Toggles sort direction or changes sort column.
  // @PRE: column name is provided.
  // @POST: sortColumn and sortDirection state updated.
  function handleSort(column: keyof DashboardMetadata) {
    if (sortColumn === column) {
      sortDirection = sortDirection === "asc" ? "desc" : "asc";
    } else {
      sortColumn = column;
      sortDirection = "asc";
    }
  }
  // #endregion handleSort:Function

  // #region handleSelectionChange:Function [TYPE Function]
  // @PURPOSE: Handles individual checkbox changes.
  // @PRE: dashboard ID and checked status provided.
  // @POST: selectedIds array updated.
  function handleSelectionChange(id: number, checked: boolean) {
    let newSelected = [...selectedIds];
    if (checked) {
      if (!newSelected.includes(id)) newSelected.push(id);
    } else {
      newSelected = newSelected.filter((sid) => sid !== id);
    }
    selectedIds = newSelected;
  }
  // #endregion handleSelectionChange:Function

  // #region handleSelectAll:Function [TYPE Function]
  // @PURPOSE: Handles select all checkbox.
  // @PRE: checked status provided.
  // @POST: selectedIds array updated for all paginated items.
  function handleSelectAll(checked: boolean) {
    let newSelected = [...selectedIds];
    if (checked) {
      paginatedDashboards.forEach((d) => {
        if (!newSelected.includes(d.id)) newSelected.push(d.id);
      });
    } else {
      paginatedDashboards.forEach((d) => {
        newSelected = newSelected.filter((sid) => sid !== d.id);
      });
    }
    selectedIds = newSelected;
  }
  // #endregion handleSelectAll:Function

  // #region goToPage:Function [TYPE Function]
  // @PURPOSE: Changes current page.
  // @PRE: page index is provided.
  // @POST: currentPage state updated if within valid range.
  function goToPage(page: number) {
    if (page >= 0 && page < totalPages) {
      currentPage = page;
    }
  }
  // #endregion goToPage:Function

  // #region getRepositoryStatusToken:Function [TYPE Function]
  /**
   * @purpose Returns normalized repository status token for a dashboard.
   * @pre Dashboard exists.
   * @post Returns one of loading|no_repo|synced|changes|behind_remote|ahead_remote|diverged|error.
   */
  function getRepositoryStatusToken(dashboardId: number): string {
    return repositoryStatusByDashboardId[dashboardId] || "loading";
  }
  // #endregion getRepositoryStatusToken:Function

  // #region isRepositoryReady:Function [TYPE Function]
  // @PURPOSE: Determines whether git actions can run for a dashboard.
  function isRepositoryReady(dashboardId: number): boolean {
    const token = getRepositoryStatusToken(dashboardId);
    return token !== "loading" && token !== "no_repo" && token !== "error";
  }
  // #endregion isRepositoryReady:Function

  // #region invalidateRepositoryStatuses:Function [TYPE Function]
  // @PURPOSE: Marks dashboard statuses as loading so they are refetched.
  function invalidateRepositoryStatuses(dashboardIds: number[]): void {
    if (dashboardIds.length === 0) return;
    const nextStatuses = { ...repositoryStatusByDashboardId };
    dashboardIds.forEach((dashboardId) => {
      nextStatuses[dashboardId] = "loading";
    });
    repositoryStatusByDashboardId = nextStatuses;
  }
  // #endregion invalidateRepositoryStatuses:Function

  // #region resolveRepositoryStatusToken:Function [TYPE Function]
  /**
   * @purpose Converts git status payload into a stable UI status token.
   */
  function resolveRepositoryStatusToken(status: any): string {
    const syncState = String(status?.sync_state || "").toUpperCase();
    if (syncState === "DIVERGED") return "diverged";
    if (syncState === "BEHIND_REMOTE") return "behind_remote";
    if (syncState === "AHEAD_REMOTE") return "ahead_remote";
    if (syncState === "CHANGES") return "changes";
    if (syncState === "SYNCED") return "synced";

    const syncStatus = String(status?.sync_status || "").toUpperCase();
    if (syncStatus === "NO_REPO") return "no_repo";
    if (syncStatus === "ERROR") return "error";
    if (syncStatus === "DIFF") return "changes";
    if (syncStatus === "OK") return "synced";

    const aheadCount = Number(status?.ahead_count || 0);
    const behindCount = Number(status?.behind_count || 0);
    if (aheadCount > 0 && behindCount > 0) return "diverged";
    if (behindCount > 0) return "behind_remote";
    if (aheadCount > 0) return "ahead_remote";

    const hasChanges =
      Boolean(status?.is_dirty) ||
      (status?.untracked_files?.length || 0) > 0 ||
      (status?.modified_files?.length || 0) > 0 ||
      (status?.staged_files?.length || 0) > 0;

    return hasChanges ? "changes" : "synced";
  }
  // #endregion resolveRepositoryStatusToken:Function

  // #region loadRepositoryStatuses:Function [TYPE Function]
  /**
   * @purpose Hydrates repository status map for dashboards in repository mode.
   */
  async function loadRepositoryStatuses() {
    if (statusMode !== "repository") {
      repositoryStatusByDashboardId = {};
      return;
    }

    const requestId = repositoryStatusRequestId + 1;
    repositoryStatusRequestId = requestId;

    const visibleDashboards = paginatedDashboards;
    const visibleIds = visibleDashboards.map((dashboard) => dashboard.id);

    if (visibleDashboards.length === 0) {
      repositoryStatusByDashboardId = {};
      return;
    }

    const missingIds = visibleIds.filter((dashboardId) => {
      const token = repositoryStatusByDashboardId[dashboardId];
      return token === undefined || token === "loading";
    });

    if (missingIds.length === 0) {
      return;
    }

    const loadingStatuses = { ...repositoryStatusByDashboardId };
    missingIds.forEach((dashboardId) => {
      loadingStatuses[dashboardId] = "loading";
    });
    repositoryStatusByDashboardId = loadingStatuses;

    let entries: Array<readonly [number, string]> = [];
    try {
      const batchResult = await gitService.getStatusesBatch(missingIds);
      const statusesByDashboardId = batchResult?.statuses || {};
      entries = missingIds.map((dashboardId) => {
        const status =
          statusesByDashboardId[dashboardId] ??
          statusesByDashboardId[String(dashboardId)];
        if (!status) return [dashboardId, "error"] as const;
        return [dashboardId, resolveRepositoryStatusToken(status)] as const;
      });
    } catch (error) {
      entries = missingIds.map((dashboardId) => [dashboardId, "error"] as const);
    }

    if (requestId !== repositoryStatusRequestId) return;
    repositoryStatusByDashboardId = {
      ...repositoryStatusByDashboardId,
      ...Object.fromEntries(entries),
    };
  }
  // #endregion loadRepositoryStatuses:Function

  // #region runBulkGitAction:Function [TYPE Function]
  // @PURPOSE: Executes git action for selected dashboards with limited parallelism.
  async function runBulkGitAction(
    actionToken: string,
    action: (dashboardId: number) => Promise<void>,
  ): Promise<void> {
    if (bulkActionRunning) return;
    const selectedDashboardIds = selectedIds.filter((dashboardId) =>
      isRepositoryReady(dashboardId),
    );

    if (selectedDashboardIds.length === 0) {
      addToast($t.git?.no_repositories_selected, "error");
      return;
    }

    bulkActionRunning = true;
    const concurrency = 3;
    const idsQueue = [...selectedDashboardIds];
    let successCount = 0;
    let failedCount = 0;

    const worker = async () => {
      while (idsQueue.length > 0) {
        const dashboardId = idsQueue.shift();
        if (dashboardId === undefined) break;
        try {
          await action(dashboardId);
          successCount += 1;
        } catch (_error) {
          failedCount += 1;
        }
      }
    };

    try {
      await Promise.all(
        Array.from({ length: Math.min(concurrency, selectedDashboardIds.length) }, () => worker()),
      );
      invalidateRepositoryStatuses(selectedDashboardIds);
      const actionLabel = $t.git?.[`bulk_action_${actionToken}`] || actionToken;
      addToast(
        $t.git?.bulk_result
          .replace("{action}", actionLabel)
          .replace("{success}", String(successCount))
          .replace("{failed}", String(failedCount)),
        failedCount > 0 ? "warning" : "success",
      );
    } finally {
      bulkActionRunning = false;
    }
  }
  // #endregion runBulkGitAction:Function

  // #region handleBulkSync:Function [TYPE Function]
  async function handleBulkSync(): Promise<void> {
    await runBulkGitAction("sync", (dashboardId) => gitService.sync(dashboardId, null, envId));
  }
  // #endregion handleBulkSync:Function

  // #region handleBulkCommit:Function [TYPE Function]
  async function handleBulkCommit(): Promise<void> {
    const message = prompt($t.git?.commit_message);
    if (!message?.trim()) return;
    await runBulkGitAction("commit", (dashboardId) =>
      gitService.commit(dashboardId, message.trim(), [], envId),
    );
  }
  // #endregion handleBulkCommit:Function

  // #region handleBulkPull:Function [TYPE Function]
  async function handleBulkPull(): Promise<void> {
    await runBulkGitAction("pull", (dashboardId) => gitService.pull(dashboardId, envId));
  }
  // #endregion handleBulkPull:Function

  // #region handleBulkPush:Function [TYPE Function]
  async function handleBulkPush(): Promise<void> {
    await runBulkGitAction("push", (dashboardId) => gitService.push(dashboardId, envId));
  }
  // #endregion handleBulkPush:Function

  // #region handleBulkDelete:Function [TYPE Function]
  // @PURPOSE: Removes selected repositories from storage and binding table.
  async function handleBulkDelete(): Promise<void> {
    if (!confirm($t.git?.confirm_delete_repo || "Удалить выбранные репозитории?")) return;
    const idsToDelete = [...selectedIds];
    await runBulkGitAction("delete", (dashboardId) =>
      gitService.deleteRepository(dashboardId, envId),
    );
    dashboards = dashboards.filter((dashboard) => !idsToDelete.includes(dashboard.id));
    selectedIds = [];
  }
  // #endregion handleBulkDelete:Function

  // #region handleManageSelected:Function [TYPE Function]
  // @PURPOSE: Opens Git manager for exactly one selected dashboard.
  async function handleManageSelected(): Promise<void> {
    if (selectedIds.length !== 1) {
      addToast($t.git?.select_single_for_manage, "warning");
      return;
    }

    const selectedDashboardId = selectedIds[0];
    const selectedDashboard = dashboards.find(
      (dashboard) => dashboard.id === selectedDashboardId,
    );
    openGitManagerForDashboard(selectedDashboard || null);
  }
  // #endregion handleManageSelected:Function

  // #region resolveDashboardRef:Function [TYPE Function]
  // @PURPOSE: Resolves dashboard slug from payload fields.
  // @PRE: Dashboard metadata is provided.
  // @POST: Returns slug string or null if unavailable.
  function resolveDashboardRef(dashboard: DashboardMetadata): string | null {
    const directSlug = String(
      dashboard.slug ||
      dashboard.dashboard_slug ||
      dashboard.url_slug ||
      "",
    ).trim();
    if (directSlug) return directSlug;

    const dashboardUrl = String(dashboard.url || "").trim();
    if (!dashboardUrl) return null;
    const slugMatch = dashboardUrl.match(/\/dashboard\/([^/?#]+)/i);
    if (!slugMatch?.[1]) return null;
    return decodeURIComponent(slugMatch[1]);
  }
  // #endregion resolveDashboardRef:Function

  // #region openGitManagerForDashboard:Function [TYPE Function]
  // @PURPOSE: Opens Git manager for provided dashboard metadata.
  function openGitManagerForDashboard(dashboard: DashboardMetadata | null): void {
    if (!dashboard) return;
    const dashboardRef = resolveDashboardRef(dashboard);
    if (!dashboardRef) {
      addToast($t.git?.select_dashboard_with_slug || "Dashboard slug is required to open GitManager", "error");
      return;
    }
    gitDashboardId = dashboardRef;
    gitDashboardTitle = dashboard.title || "";
    showGitManager = true;
  }
  // #endregion openGitManagerForDashboard:Function

  // #region handleInitializeRepositories:Function [TYPE Function]
  // @PURPOSE: Opens Git manager from bulk actions to initialize selected repository.
  async function handleInitializeRepositories(): Promise<void> {
    if (selectedIds.length !== 1) {
      addToast($t.git?.select_single_for_manage, "warning");
      return;
    }
    const selectedDashboardId = selectedIds[0];
    const selectedDashboard = dashboards.find((dashboard) => dashboard.id === selectedDashboardId) || null;
    openGitManagerForDashboard(selectedDashboard);
  }
  // #endregion handleInitializeRepositories:Function

  // #region getSortStatusValue:Function [TYPE Function]
  /**
   * @purpose Returns sort value for status column based on mode.
   */
  function getSortStatusValue(dashboard: DashboardMetadata): string {
    if (statusMode === "repository") {
      return getRepositoryStatusToken(dashboard.id);
    }
    return String(dashboard.status || "").toLowerCase();
  }
  // #endregion getSortStatusValue:Function

  // #region getStatusLabel:Function [TYPE Function]
  /**
   * @purpose Returns localized label for status column.
   */
  function getStatusLabel(dashboard: DashboardMetadata): string {
    if (statusMode !== "repository") {
      return String(dashboard.status || "");
    }
    const token = getRepositoryStatusToken(dashboard.id);
    return $t.git?.repo_status?.[token] || token;
  }
  // #endregion getStatusLabel:Function

  // #region getStatusBadgeClass:Function [TYPE Function]
  /**
   * @purpose Returns badge style for status column.
   */
  function getStatusBadgeClass(dashboard: DashboardMetadata): string {
    if (statusMode !== "repository") {
      return dashboard.status === "published"
        ? "bg-green-100 text-green-800"
        : "bg-gray-100 text-gray-800";
    }

    const token = getRepositoryStatusToken(dashboard.id);
    if (token === "loading") return "bg-slate-100 text-slate-500";
    if (token === "no_repo") return "bg-gray-100 text-gray-800";
    if (token === "synced") return "bg-green-100 text-green-800";
    if (token === "changes") return "bg-amber-100 text-amber-800";
    if (token === "behind_remote") return "bg-blue-100 text-blue-800";
    if (token === "ahead_remote") return "bg-indigo-100 text-indigo-800";
    if (token === "diverged") return "bg-purple-100 text-purple-800";
    return "bg-rose-100 text-rose-800";
  }
  // #endregion getStatusBadgeClass:Function

  $effect(() => {
    dashboards;
    statusMode;
    currentPage;
    pageSize;
    filterText;
    sortColumn;
    sortDirection;
    untrack(() => {
      void loadRepositoryStatuses();
    });
  });
</script>

<!-- [SECTION: TEMPLATE] -->
<div class="dashboard-grid">
  <!-- Filter Input -->
  <div class="mb-6">
    <Input bind:value={filterText} placeholder={$t.dashboard.search} />
  </div>

  {#if selectedIds.length > 0}
    <div class="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-blue-100 bg-primary-light/60 px-3 py-2">
      {#if !repositoriesOnly}
        <Button
          size="sm"
          variant="secondary"
          onclick={handleManageSelected}
          disabled={bulkActionRunning || selectedIds.length !== 1}
          class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50 disabled:opacity-40"
        >
          {$t.git?.manage_selected}
        </Button>
        <Button
          size="sm"
          variant="secondary"
          onclick={handleInitializeRepositories}
          disabled={bulkActionRunning || selectedIds.length !== 1}
          class="border-slate-200 bg-white text-slate-700 hover:bg-slate-50 disabled:opacity-40"
        >
          {$t.git?.init_repo || "Инициализировать Git-репозиторий"}
        </Button>
      {/if}
      <Button size="sm" variant="secondary" onclick={handleBulkSync} disabled={bulkActionRunning} class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50">
        {$t.git?.bulk_sync}
      </Button>
      {#if !repositoriesOnly}
        <Button size="sm" variant="secondary" onclick={handleBulkCommit} disabled={bulkActionRunning} class="border-amber-200 bg-white text-amber-700 hover:bg-amber-50">
          {$t.git?.bulk_commit}
        </Button>
      {/if}
      <Button size="sm" variant="secondary" onclick={handleBulkPull} disabled={bulkActionRunning} class="border-cyan-200 bg-white text-cyan-700 hover:bg-cyan-50">
        {$t.git?.bulk_pull}
      </Button>
      <Button size="sm" variant="secondary" onclick={handleBulkPush} disabled={bulkActionRunning} class="border-indigo-200 bg-white text-indigo-700 hover:bg-indigo-50">
        {$t.git?.bulk_push}
      </Button>
      {#if repositoriesOnly}
        <Button size="sm" variant="secondary" onclick={handleBulkDelete} disabled={bulkActionRunning} class="border-rose-200 bg-white text-rose-700 hover:bg-rose-50">
          {$t.git?.delete_repo || "Удалить репозиторий"}
        </Button>
      {/if}
      <span class="ml-1 text-xs font-medium text-slate-600">
        {$t.git?.selected_count.replace(
          "{count}",
          String(selectedIds.length),
        )}
      </span>
    </div>
  {/if}

  <!-- Grid/Table -->
  <div class="overflow-x-auto rounded-lg border border-gray-200">
    <table class="min-w-full divide-y divide-gray-200">
      <thead class="bg-gray-50">
        <tr>
          <th class="px-6 py-3 text-left">
            <input
              type="checkbox"
              checked={allSelected}
              indeterminate={someSelected && !allSelected}
              onchange={(e) =>
                handleSelectAll((e.target as HTMLInputElement).checked)}
              class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
            />
          </th>
          <th
            class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
            onclick={() => handleSort("title")}
          >
            {$t.dashboard.title}
            {sortColumn === "title"
              ? sortDirection === "asc"
                ? "↑"
                : "↓"
              : ""}
          </th>
          <th
            class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
            onclick={() => handleSort("last_modified")}
          >
            {$t.dashboard.last_modified}
            {sortColumn === "last_modified"
              ? sortDirection === "asc"
                ? "↑"
                : "↓"
              : ""}
          </th>
          <th
            class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
            onclick={() => handleSort("status")}
          >
            {$t.dashboard.status}
            {sortColumn === "status"
              ? sortDirection === "asc"
                ? "↑"
                : "↓"
              : ""}
          </th>
        </tr>
      </thead>
      <tbody class="bg-white divide-y divide-gray-200">
        {#each paginatedDashboards as dashboard (dashboard.id)}
          <tr class="hover:bg-gray-50 transition-colors">
            <td class="px-6 py-4 whitespace-nowrap">
              <input
                type="checkbox"
                checked={selectedIds.includes(dashboard.id)}
                onchange={(e) =>
                  handleSelectionChange(
                    dashboard.id,
                    (e.target as HTMLInputElement).checked,
                  )}
                class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
              />
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
              <button
                type="button"
                class="text-left text-blue-700 hover:text-blue-900 hover:underline"
                onclick={() => openGitManagerForDashboard(dashboard)}
              >
                {dashboard.title}
              </button>
            </td>
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
              >{new Date(dashboard.last_modified).toLocaleDateString()}</td
            >
            <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
              <span
                class="px-2 py-1 text-xs font-medium rounded-full {getStatusBadgeClass(dashboard)}"
              >
                {getStatusLabel(dashboard)}
              </span>
            </td>
          </tr>
        {/each}
      </tbody>
    </table>
  </div>

  <!-- Pagination Controls -->
  <div class="flex items-center justify-between mt-6">
    <div class="text-sm text-gray-500">
      {($t.dashboard?.showing )
        .replace("{start}", (currentPage * pageSize + 1).toString())
        .replace(
          "{end}",
          Math.min(
            (currentPage + 1) * pageSize,
            sortedDashboards.length,
          ).toString(),
        )
        .replace("{total}", sortedDashboards.length.toString())}
    </div>
    <div class="flex gap-2">
      <Button
        variant="secondary"
        size="sm"
        disabled={currentPage === 0}
        onclick={() => goToPage(currentPage - 1)}
      >
        {$t.dashboard.previous}
      </Button>
      <Button
        variant="secondary"
        size="sm"
        disabled={currentPage >= totalPages - 1}
        onclick={() => goToPage(currentPage + 1)}
      >
        {$t.dashboard.next}
      </Button>
    </div>
  </div>
</div>

{#if showGitManager && gitDashboardId}
  <GitManager
    dashboardId={gitDashboardId}
    envId={envId}
    dashboardTitle={gitDashboardTitle}
    bind:show={showGitManager}
  />
{/if}

<!-- [/SECTION] -->

<!-- #endregion RepositoryDashboardGrid -->
