tenantpilot/lib/utils/policy-table-helpers.ts
Ahmed Darrazi aa598452e9
All checks were successful
Trigger Cloudarix Deploy / call-webhook (push) Successful in 2s
feat(policy-explorer-v2): Phase 6 - Enhanced Detail View
Implemented Tasks T038-T046:
- T038: Created useCopyToClipboard hook with toast notifications
- T039: Skipped (unit tests - optional)
- T040: Added copy button for Policy ID field
- T041: Added copy button for Setting Name field
- T042: Added tabs for Details and Raw JSON views
- T043: Implemented Raw JSON tab with syntax highlighting
- T044: Created getIntunePortalLink utility (8 policy types)
- T045: Added Open in Intune button with URL construction
- T046: Fallback to copy Policy ID if URL unavailable

Files Created:
- lib/hooks/useCopyToClipboard.ts (65 lines)
- lib/utils/policy-table-helpers.ts (127 lines)

Files Updated:
- components/policy-explorer/PolicyDetailSheet.tsx (enhanced with tabs, copy buttons, Intune links)

Features:
- Copy-to-clipboard for all fields with visual feedback
- Two-tab interface: Details (enhanced fields) and Raw JSON (full object)
- Deep linking to Intune portal by policy type
- Clipboard API with document.execCommand fallback
- Toast notifications for user feedback
2025-12-10 00:40:09 +01:00

116 lines
4.2 KiB
TypeScript

/**
* Policy Table Helper Functions
*
* Utilities for formatting, sorting, and generating links for policy data.
*/
import type { PolicySettingRow } from '@/lib/types/policy-table';
/**
* Generate Intune Portal URL for a policy based on its type and ID
* @param policyType The type of policy (e.g., 'deviceConfiguration')
* @param graphPolicyId The Microsoft Graph policy ID
* @returns Intune Portal URL or null if URL construction fails
*/
export function getIntunePortalLink(
policyType: string,
graphPolicyId: string
): string | null {
if (!graphPolicyId) {
return null;
}
const baseUrl = 'https://intune.microsoft.com';
// Map policy types to Intune portal paths
const policyTypeUrls: Record<string, string> = {
// Device Configuration
deviceConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceConfigProfilesMenu/~/properties/policyId/${graphPolicyId}`,
// Compliance Policies
compliancePolicy: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceCompliancePoliciesMenu/~/properties/policyId/${graphPolicyId}`,
// Device Management Scripts
deviceManagementScript: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceManagementScriptsMenu/~/properties/scriptId/${graphPolicyId}`,
// Windows Update for Business
windowsUpdateForBusiness: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/UpdateRingsMenu/~/properties/policyId/${graphPolicyId}`,
// iOS Update Configuration
iosUpdateConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/iOSUpdateConfigurationsMenu/~/properties/policyId/${graphPolicyId}`,
// Settings Catalog
settingsCatalog: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/ConfigurationPolicyMenu/~/properties/policyId/${graphPolicyId}`,
// Endpoint Protection
endpointProtection: `${baseUrl}/#view/Microsoft_Intune_Workflows/SecurityBaselinesSummaryMenu/~/properties/templateId/${graphPolicyId}`,
// macOS Extensions
macOSExtensionsConfiguration: `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceConfigProfilesMenu/~/properties/policyId/${graphPolicyId}`,
};
// Check if we have a known URL pattern
if (policyType in policyTypeUrls) {
return policyTypeUrls[policyType];
}
// Fallback: Generic device settings URL
return `${baseUrl}/#view/Microsoft_Intune_DeviceSettings/DeviceSettingsMenu`;
}
/**
* Format policy type for display (convert camelCase to Title Case)
* @param policyType Policy type in camelCase (e.g., 'deviceConfiguration')
* @returns Formatted title (e.g., 'Device Configuration')
*/
export function formatPolicyType(policyType: string): string {
// Insert spaces before capital letters
const withSpaces = policyType.replace(/([A-Z])/g, ' $1');
// Capitalize first letter and trim
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1).trim();
}
/**
* Truncate long text with ellipsis
* @param text Text to truncate
* @param maxLength Maximum length before truncation
* @returns Truncated text with ellipsis if needed
*/
export function truncateText(text: string, maxLength: number = 100): string {
if (text.length <= maxLength) {
return text;
}
return text.substring(0, maxLength) + '...';
}
/**
* Format date for display (relative or absolute)
* @param date Date to format
* @param relative Whether to show relative time (e.g., "2 hours ago")
* @returns Formatted date string
*/
export function formatDate(date: Date | string, relative: boolean = false): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
if (relative) {
const now = new Date();
const diffMs = now.getTime() - dateObj.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return 'just now';
if (diffMins < 60) return `${diffMins} min${diffMins > 1 ? 's' : ''} ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
const diffDays = Math.floor(diffHours / 24);
if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
const diffWeeks = Math.floor(diffDays / 7);
return `${diffWeeks} week${diffWeeks > 1 ? 's' : ''} ago`;
}
return dateObj.toLocaleString();
}