frontend_nyla/src/pages/Home/TestSharepoint.tsx

432 lines
No EOL
23 KiB
TypeScript

import { useState } from 'react';
import { IoIosRefresh, IoIosLink } from 'react-icons/io';
import { useLanguage } from '../../contexts/LanguageContext';
import sharedStyles from '../../components/PageManager/pages.module.css';
import styles from './HomeStyles/TestSharepoint.module.css'
import { TestSharepointTable, useTestSharepointLogic } from '../../components/TestSharepoint'
function TestSharepoint() {
const { t } = useLanguage();
const {
connections,
selectedConnection,
connectionLoading,
connectionError,
documents,
documentsLoading,
documentsError,
columns,
actions,
testingConnections,
connectionTestResults,
discoveredSites,
sitesDiscovered,
tokenDebugInfo,
handleSelectConnection,
handleTestConnection,
handleListDocuments,
handleDiscoverSites,
handleSelectSite,
handleDebugTokens,
handleCleanupTokens,
handleFolderNavigation,
refetchConnections
} = useTestSharepointLogic();
const [tableRefreshKey, setTableRefreshKey] = useState(0);
const [siteUrl, setSiteUrl] = useState('https://your-tenant.sharepoint.com/sites/your-site');
const [folderPaths, setFolderPaths] = useState(['/']);
const onTestConnection = async (connectionId: string) => {
await handleTestConnection(connectionId);
};
const onListDocuments = async () => {
console.log('onListDocuments called with:', { siteUrl, folderPaths });
await handleListDocuments(siteUrl, folderPaths);
// Force table refresh to show new data
const newKey = tableRefreshKey + 1;
console.log('Setting tableRefreshKey to:', newKey);
setTableRefreshKey(newKey);
};
const onDiscoverSites = async () => {
await handleDiscoverSites();
};
const onSelectSite = (selectedSiteUrl: string) => {
setSiteUrl(selectedSiteUrl);
handleSelectSite(selectedSiteUrl);
};
const onRowClick = (row: any) => {
console.log('Row clicked:', row);
if (row.type === 'folder') {
const currentPath = folderPaths[0] || '/';
const newPath = handleFolderNavigation(row, currentPath);
console.log('Navigating from', currentPath, 'to', newPath);
setFolderPaths([newPath]);
// Automatically refresh the document list with the new path
handleListDocuments(siteUrl, [newPath]);
setTableRefreshKey(prev => prev + 1);
}
};
const renderConnectionCard = (connection: any) => {
const testResult = connectionTestResults[connection.id];
const isTestingThis = testingConnections.has(connection.id);
return (
<div
key={connection.id}
className={`${styles.connectionCard} ${selectedConnection?.id === connection.id ? styles.active : ''}`}
onClick={() => handleSelectConnection(connection.id)}
>
<div className={styles.connectionInfo}>
<div className={styles.connectionName}>
{connection.externalUsername || connection.id}
</div>
<div className={styles.connectionStatus}>
<span className={`${styles.statusBadge} ${styles[connection.status]}`}>
{connection.status}
</span>
{connection.externalEmail && (
<span style={{ marginLeft: '8px' }}>{connection.externalEmail}</span>
)}
</div>
</div>
<div className={styles.connectionActions}>
<button
className={styles.button}
onClick={(e) => {
e.stopPropagation();
onTestConnection(connection.id);
}}
disabled={isTestingThis}
aria-label={t('sharepoint.button.testConnection')}
>
{isTestingThis ? '⏳' : t('sharepoint.button.testConnection')}
</button>
{testResult && (
<span className={testResult.success ? styles.successMessage : styles.errorMessage}>
{testResult.success ? '✓' : '✗'}
</span>
)}
</div>
</div>
);
};
return (
<div className={sharedStyles.pageContainer}>
<div className={sharedStyles.contentWrapper}>
<div className={sharedStyles.pageCard}>
<div className={sharedStyles.pageHeader}>
<h1 className={sharedStyles.pageTitle}>{t('sharepoint.title')}</h1>
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
<button
className={sharedStyles.primaryButton}
onClick={refetchConnections}
disabled={connectionLoading}
aria-label="Refresh connections"
>
<span className={sharedStyles.buttonIcon}><IoIosRefresh /></span>
{connectionLoading ? 'Loading...' : 'Refresh Connections'}
</button>
<button
className={sharedStyles.secondaryButton}
onClick={handleDebugTokens}
disabled={connectionLoading}
aria-label="Debug Token Info"
>
🔍 Debug Token
</button>
</div>
</div>
<div className={sharedStyles.horizontalDivider}></div>
{/* Connections Section */}
<div className={styles.section}>
<h2 className={styles.sectionTitle}>
{t('sharepoint.connections.title')} ({connections.length})
</h2>
{connectionError && (
<div className={styles.errorMessage}>
{connectionError}
</div>
)}
{connections.length === 0 ? (
<div className={styles.loading}>
{connectionLoading ?
t('sharepoint.connections.loading') :
t('sharepoint.connections.noConnections')
}
</div>
) : (
<div className={styles.connectionsGrid}>
{connections.map(renderConnectionCard)}
</div>
)}
</div>
{/* Token Debug Section */}
{tokenDebugInfo && (
<>
<div className={sharedStyles.horizontalDivider}></div>
<div className={styles.section}>
<h2 className={styles.sectionTitle}>🔍 Token Debug Information</h2>
<div style={{ background: 'var(--color-bg-secondary, #f8f9fa)', padding: '15px', borderRadius: '8px', border: '1px solid var(--color-border, #dee2e6)' }}>
<div style={{ marginBottom: '10px' }}>
<strong>User ID:</strong> {tokenDebugInfo.data?.userId || 'Unknown'}
</div>
<div style={{ marginBottom: '10px' }}>
<strong>All Tokens Count:</strong> {tokenDebugInfo.data?.allTokensCount || 0}
</div>
{tokenDebugInfo.data?.allTokens && tokenDebugInfo.data.allTokens.length > 0 && (
<div style={{ marginBottom: '15px' }}>
<strong>Microsoft Tokens:</strong>
{tokenDebugInfo.data.allTokens.map((token: any, index: number) => (
<div key={index} style={{ marginLeft: '15px', marginTop: '8px', padding: '8px', background: token.isExpired ? 'var(--color-error-bg, #ffe6e6)' : 'var(--color-success-bg, #e6ffe6)', borderRadius: '4px' }}>
<div><strong>Token ID:</strong> {token.id}</div>
<div><strong>Authority:</strong> {token.authority}</div>
<div><strong>Expires At:</strong> {new Date(token.expiresAt * 1000).toLocaleString()}</div>
<div><strong>Is Expired:</strong> {token.isExpired ? '❌ YES' : '✅ NO'}</div>
<div><strong>Has Access Token:</strong> {token.hasAccessToken ? '✅ YES' : '❌ NO'}</div>
<div><strong>Has Refresh Token:</strong> {token.hasRefreshToken ? '✅ YES' : '❌ NO'}</div>
</div>
))}
</div>
)}
{tokenDebugInfo.data?.sharepointMethodToken && (
<div style={{ marginTop: '15px', padding: '10px', background: 'var(--color-warning-bg, #fff3cd)', borderRadius: '4px' }}>
<strong>SharePoint Method Token Status:</strong>
<div style={{ marginLeft: '15px', marginTop: '5px' }}>
{tokenDebugInfo.data.sharepointMethodToken.tokenFound ? (
<div>
<div> Token Found</div>
<div><strong>Token ID:</strong> {tokenDebugInfo.data.sharepointMethodToken.tokenId}</div>
<div><strong>Expires At:</strong> {new Date(tokenDebugInfo.data.sharepointMethodToken.expiresAt * 1000).toLocaleString()}</div>
<div><strong>Is Expired:</strong> {tokenDebugInfo.data.sharepointMethodToken.isExpired ? '❌ YES' : '✅ NO'}</div>
</div>
) : (
<div> No Token Found: {tokenDebugInfo.data.sharepointMethodToken.reason || tokenDebugInfo.data.sharepointMethodToken.error}</div>
)}
</div>
</div>
)}
{/* Provide action recommendations */}
<div style={{ marginTop: '15px', padding: '12px', background: 'var(--color-info-bg, #e3f2fd)', borderRadius: '4px', border: '1px solid var(--color-info, #2196f3)' }}>
<strong>💡 Recommendation:</strong>
<div style={{ marginTop: '8px' }}>
{tokenDebugInfo.data?.allTokens?.some((token: any) => token.isExpired) ? (
<div>
🔄 <strong>Your tokens are stale!</strong> The tokens weren't properly cleared. Use the button below to force cleanup:
<div style={{ marginTop: '12px' }}>
<button
onClick={handleCleanupTokens}
style={{
padding: '8px 16px',
background: 'var(--color-error, #d32f2f)',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: 'bold'
}}
>
🗑️ Force Delete All Tokens
</button>
</div>
<div style={{ marginTop: '8px', fontSize: '12px' }}>
After cleanup: Go to Connections page → Reconnect Microsoft account
</div>
</div>
) : !tokenDebugInfo.data?.allTokens?.some((token: any) => token.hasAccessToken) ? (
<div>
⚠️ <strong>No valid access tokens found.</strong> Please reconnect your Microsoft account in the Connections page.
</div>
) : (
<div>
✅ <strong>Tokens look valid.</strong> The issue might be with SharePoint permissions or scopes.
</div>
)}
</div>
</div>
</div>
</div>
</>
)}
{/* SharePoint Configuration Section */}
{selectedConnection && (
<>
<div className={sharedStyles.horizontalDivider}></div>
<div className={styles.section}>
<h2 className={styles.sectionTitle}>SharePoint Configuration</h2>
<div className={styles.formGroup}>
<label className={styles.label}>{t('sharepoint.form.siteUrl')}</label>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center', marginBottom: '10px' }}>
<input
type="text"
className={styles.input}
value={siteUrl}
onChange={(e) => setSiteUrl(e.target.value)}
placeholder="https://your-tenant.sharepoint.com/sites/your-site"
style={{ flex: 1 }}
/>
<button
className={sharedStyles.secondaryButton}
onClick={onDiscoverSites}
disabled={connectionLoading}
type="button"
>
<span className={sharedStyles.buttonIcon}><IoIosLink /></span>
{t('sharepoint.button.discoverSites')}
</button>
</div>
{sitesDiscovered && discoveredSites.length > 0 && (
<div className={styles.sitesDiscovery}>
<label className={styles.label}>
{t('sharepoint.sites.discovered')} ({discoveredSites.length}):
</label>
<div className={styles.sitesList}>
{discoveredSites.map((site, index) => (
<div
key={index}
className={`${styles.siteItem} ${siteUrl === site.url ? styles.selectedSite : ''}`}
onClick={() => onSelectSite(site.url)}
>
<div className={styles.siteName}>
<strong>{site.name}</strong>
<span className={styles.siteType}>({site.type})</span>
</div>
<div className={styles.siteUrl}>{site.url}</div>
{site.description && (
<div className={styles.siteDescription}>{site.description}</div>
)}
</div>
))}
</div>
</div>
)}
{sitesDiscovered && discoveredSites.length === 0 && (
<div className={styles.noSitesFound}>
<strong>{t('sharepoint.sites.noSites')}</strong>
{documentsError && (
<div style={{ marginTop: '10px' }}>
<div style={{ fontSize: '12px', color: 'var(--color-error, #d32f2f)', marginBottom: '8px' }}>
<strong>Technical details:</strong> {documentsError}
</div>
{documentsError.includes('401') || documentsError.includes('InvalidAuthenticationToken') ? (
<div style={{ fontSize: '13px', color: 'var(--color-text)', background: 'var(--color-warning-bg, #fff3cd)', padding: '8px', borderRadius: '4px', border: '1px solid var(--color-warning, #ffc107)' }}>
<div>⚠️ {t('sharepoint.sites.authError')}</div>
<div style={{ marginTop: '4px' }}>{t('sharepoint.sites.retryConnection')}</div>
</div>
) : (
<div style={{ fontSize: '12px', color: 'var(--color-text-secondary)' }}>
Please check your Microsoft connection and permissions.
</div>
)}
</div>
)}
</div>
)}
</div>
<div className={styles.formGroup}>
<label className={styles.label}>{t('sharepoint.form.folderPaths')}</label>
<textarea
className={styles.textarea}
value={folderPaths.join('\n')}
onChange={(e) => setFolderPaths(e.target.value.split('\n').filter(path => path.trim()))}
placeholder="/&#10;/Documents&#10;/Sites/YourSite/Shared Documents"
rows={3}
/>
</div>
<div className={styles.buttonGroup}>
<button
className={sharedStyles.primaryButton}
onClick={onListDocuments}
disabled={connectionLoading || !selectedConnection}
>
{t('sharepoint.button.listDocuments')}
</button>
</div>
</div>
</>
)}
{/* Documents Table Section */}
<div className={sharedStyles.horizontalDivider}></div>
{/* Breadcrumb Navigation */}
<div className={styles.section}>
<div className={styles.breadcrumb}>
<span className={styles.breadcrumbLabel}>Current Path:</span>
{folderPaths[0] === '/' ? (
<span className={styles.breadcrumbItem}>📁 Root</span>
) : (
<>
<span
className={styles.breadcrumbItem + ' ' + styles.breadcrumbClickable}
onClick={() => {
setFolderPaths(['/']);
handleListDocuments(siteUrl, ['/']);
setTableRefreshKey(prev => prev + 1);
}}
>
📁 Root
</span>
{folderPaths[0].split('/').filter(Boolean).map((part, index, array) => {
const pathToHere = '/' + array.slice(0, index + 1).join('/');
const isLast = index === array.length - 1;
return (
<span key={index}>
<span className={styles.breadcrumbSeparator}>/</span>
<span
className={styles.breadcrumbItem + (isLast ? '' : ' ' + styles.breadcrumbClickable)}
onClick={!isLast ? () => {
setFolderPaths([pathToHere]);
handleListDocuments(siteUrl, [pathToHere]);
setTableRefreshKey(prev => prev + 1);
} : undefined}
>
📁 {part}
</span>
</span>
);
})}
</>
)}
</div>
</div>
<div className={sharedStyles.contentArea}>
<TestSharepointTable
key={tableRefreshKey}
className={styles.sharepointTableContainer}
documents={documents}
documentsLoading={documentsLoading}
documentsError={documentsError}
columns={columns}
actions={actions}
onRowClick={onRowClick}
/>
</div>
</div>
</div>
</div>
);
}
export default TestSharepoint;