import { mdiDownload, mdiFileUploadOutline, mdiHomeCity } from '@mdi/js'; import axios from 'axios'; import Head from 'next/head'; import React, { ReactElement, useMemo, useState } from 'react'; import BaseButton from '../components/BaseButton'; import CardBox from '../components/CardBox'; import DragDropFilePicker from '../components/DragDropFilePicker'; import SectionMain from '../components/SectionMain'; import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; import { getPageTitle } from '../config'; import { portfolioWorkbookSheets } from '../helpers/portfolioWorkbookSheets'; import { hasPermission } from '../helpers/userPermissions'; import LayoutAuthenticated from '../layouts/Authenticated'; import { useAppSelector } from '../stores/hooks'; type ImportSummary = Record; type ImportResponse = { workbook: { fileName: string; sheets: Array<{ key: string; name: string; rows: number; }>; }; summary: ImportSummary; }; const createPermissions = [ 'CREATE_TENANTS', 'CREATE_ORGANIZATIONS', 'CREATE_PROPERTIES', 'CREATE_UNIT_TYPES', 'CREATE_UNITS', ]; const summaryLabels: Record = { tenants: 'Tenants', organizations: 'Organizations', properties: 'Properties', unit_types: 'Unit Types', units: 'Units', }; const PortfolioImportPage = () => { const { currentUser } = useAppSelector((state) => state.auth); const canImportWorkbook = Boolean(currentUser?.app_role?.globalAccess || (currentUser && hasPermission(currentUser, createPermissions))); const [workbookFile, setWorkbookFile] = useState(null); const [isDownloadingTemplate, setIsDownloadingTemplate] = useState(false); const [isImporting, setIsImporting] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [successMessage, setSuccessMessage] = useState(''); const [importResult, setImportResult] = useState(null); const workbookSummary = useMemo(() => { if (!importResult?.summary) { return []; } return Object.entries(importResult.summary).filter(([, value]) => value.created > 0 || value.reused > 0); }, [importResult]); const downloadTemplate = async () => { try { setIsDownloadingTemplate(true); setErrorMessage(''); const response = await axios.get('/corporate-stay-portal/portfolio-import-template', { responseType: 'blob', }); const blob = new Blob([response.data], { type: response.headers['content-type'] || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = 'portfolio-import-template.xlsx'; link.click(); window.URL.revokeObjectURL(link.href); } catch (error: any) { setErrorMessage(error?.response?.data || 'Failed to download the workbook template.'); } finally { setIsDownloadingTemplate(false); } }; const submitWorkbook = async () => { if (!workbookFile || !canImportWorkbook) { return; } try { setIsImporting(true); setErrorMessage(''); setSuccessMessage(''); setImportResult(null); const formData = new FormData(); formData.append('file', workbookFile); formData.append('filename', workbookFile.name); const response = await axios.post('/corporate-stay-portal/portfolio-import', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); setImportResult(response.data); setSuccessMessage(`Workbook imported successfully: ${response.data.workbook.fileName}`); setWorkbookFile(null); } catch (error: any) { setErrorMessage(error?.response?.data || 'Workbook import failed.'); } finally { setIsImporting(false); } }; return ( <> {getPageTitle('Portfolio Import Center')} {''}

One workbook for portfolio onboarding

Upload a single Excel workbook to create linked tenants, organizations, properties, unit types, and units in dependency order. Existing records are reused when the workbook references a matching ID or business key.

This v1 workbook flow covers portfolio setup sheets only. Negotiated rates stay on their current flow for now.

{!canImportWorkbook && (
You can review the template, but uploading requires at least one of these permissions: {createPermissions.join(', ')}.
)} {errorMessage && (
{errorMessage}
)} {successMessage && (
{successMessage}
)}
{importResult && (

Latest import summary

Processed sheets:{' '} {importResult.workbook.sheets.map((sheet) => `${sheet.name} (${sheet.rows})`).join(', ')}

{workbookSummary.map(([key, value]) => (
{summaryLabels[key] || key}
Created: {value.created}
Reused: {value.reused}
))}
)}
{portfolioWorkbookSheets.map((sheet) => { const exampleCsvRow = sheet.columns.map((column) => sheet.exampleRow[column] ?? '').join('\t'); return (

{sheet.label}

{sheet.description}

Required columns

{sheet.requiredColumns.join(', ')}

All columns

{sheet.columns.join(', ')}

Example row
{`${sheet.columns.join('\t')}
${exampleCsvRow}`}
                    
); })}
); }; PortfolioImportPage.getLayout = function getLayout(page: ReactElement) { return {page}; }; export default PortfolioImportPage;