fh / src /components /RFPManager.svelte
Varun10000's picture
Upload 57 files
d8635c9 verified
Raw
History Blame Contribute Delete
12.3 kB
<script lang="ts">
import { onMount } from 'svelte';
import { backendService, type CurrentRFPDocument } from '../utils/backendService';
import { suggestionEngine } from '../utils/suggestionEngine';
export let visible = false;
let currentRFPs: CurrentRFPDocument[] = [];
let selectedRFP: CurrentRFPDocument | null = null;
let rfpContent = '';
let insights: any = null;
let uploading = false;
let loading = false;
// File upload
let fileInput: HTMLInputElement;
onMount(() => {
if (visible) {
loadCurrentRFPs();
}
});
$: if (visible) {
loadCurrentRFPs();
}
async function loadCurrentRFPs() {
try {
loading = true;
currentRFPs = await backendService.getCurrentRFPs();
} catch (error) {
console.error('Failed to load current RFPs:', error);
} finally {
loading = false;
}
}
async function handleFileUpload(event: Event) {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;
try {
uploading = true;
const result = await backendService.uploadCurrentRFP(file);
// Add to current RFPs list
currentRFPs = [...currentRFPs, result.document];
// Auto-select the uploaded document
selectedRFP = result.document;
await loadRFPContent(result.document.id);
// Reset file input
target.value = '';
} catch (error) {
console.error('Upload failed:', error);
alert('Failed to upload file: ' + (error as Error).message);
} finally {
uploading = false;
}
}
async function selectRFP(rfp: CurrentRFPDocument) {
selectedRFP = rfp;
await loadRFPContent(rfp.id);
}
async function loadRFPContent(id: string) {
try {
loading = true;
const result = await backendService.getCurrentRFPContent(id);
rfpContent = result.content;
// Analyze the RFP document for insights
insights = suggestionEngine.analyzeRFPDocument(rfpContent);
} catch (error) {
console.error('Failed to load RFP content:', error);
} finally {
loading = false;
}
}
function formatDate(dateString: string) {
return new Date(dateString).toLocaleDateString();
}
function getFileIcon(fileType: string) {
switch (fileType.toLowerCase()) {
case 'pdf': return '📄';
case 'docx': case 'doc': return '📝';
case 'txt': return '📋';
default: return '📁';
}
}
</script>
<div class="rfp-manager" class:visible>
<div class="header">
<h3>Current RFP Documents</h3>
<button class="close-btn" on:click={() => visible = false}>×</button>
</div>
<div class="content">
<!-- Upload Section -->
<div class="upload-section">
<h4>Upload Current RFP</h4>
<div class="upload-area">
<input
bind:this={fileInput}
type="file"
accept=".pdf,.doc,.docx,.txt"
on:change={handleFileUpload}
style="display: none;"
/>
<button
class="upload-btn"
on:click={() => fileInput.click()}
disabled={uploading}
>
{uploading ? 'Uploading...' : 'Choose RFP Document'}
</button>
<p class="upload-help">Upload the current RFP document to analyze and get suggestions</p>
</div>
</div>
<!-- Documents List -->
<div class="documents-section">
<h4>Uploaded RFPs ({currentRFPs.length})</h4>
{#if loading && currentRFPs.length === 0}
<div class="loading">Loading documents...</div>
{:else if currentRFPs.length === 0}
<div class="empty-state">
No RFP documents uploaded yet. Upload one to get started!
</div>
{:else}
<div class="documents-list">
{#each currentRFPs as rfp (rfp.id)}
<div
class="document-item"
class:selected={selectedRFP?.id === rfp.id}
on:click={() => selectRFP(rfp)}
>
<div class="doc-icon">{getFileIcon(rfp.fileType)}</div>
<div class="doc-info">
<div class="doc-name">{rfp.name}</div>
<div class="doc-meta">
{rfp.fileType.toUpperCase()} • {formatDate(rfp.uploadDate)}
{#if rfp.contentLength}
• {Math.round(rfp.contentLength / 1000)}k chars
{/if}
</div>
</div>
<div class="doc-status">
{#if rfp.processed}
<span class="status-processed">✓ Processed</span>
{:else}
<span class="status-pending">⏳ Processing</span>
{/if}
</div>
</div>
{/each}
</div>
{/if}
</div>
<!-- RFP Analysis -->
{#if selectedRFP && insights}
<div class="analysis-section">
<h4>RFP Analysis: {selectedRFP.name}</h4>
<div class="insights-grid">
<!-- Key Requirements -->
<div class="insight-card">
<h5>Key Requirements</h5>
<div class="tag-list">
{#each insights.keyRequirements.slice(0, 8) as requirement}
<span class="tag">{requirement}</span>
{/each}
</div>
</div>
<!-- Technology Mentions -->
{#if insights.technologyMentions.length > 0}
<div class="insight-card">
<h5>Technology Stack</h5>
<div class="tag-list">
{#each insights.technologyMentions as tech}
<span class="tag tech-tag">{tech}</span>
{/each}
</div>
</div>
{/if}
<!-- Compliance Requirements -->
{#if insights.complianceRequirements.length > 0}
<div class="insight-card">
<h5>Compliance & Security</h5>
<div class="tag-list">
{#each insights.complianceRequirements as compliance}
<span class="tag compliance-tag">{compliance}</span>
{/each}
</div>
</div>
{/if}
<!-- Timeline Indicators -->
{#if insights.timelineIndicators.length > 0}
<div class="insight-card">
<h5>Timeline Indicators</h5>
<div class="tag-list">
{#each insights.timelineIndicators as timeline}
<span class="tag timeline-tag">{timeline}</span>
{/each}
</div>
</div>
{/if}
<!-- Budget Indicators -->
{#if insights.budgetIndicators.length > 0}
<div class="insight-card">
<h5>Budget Indicators</h5>
<div class="tag-list">
{#each insights.budgetIndicators as budget}
<span class="tag budget-tag">{budget}</span>
{/each}
</div>
</div>
{/if}
</div>
<!-- Strategy Recommendations -->
<div class="strategy-section">
<h5>Strategic Recommendations</h5>
<div class="recommendations">
<div class="recommendation">
<strong>Emphasize SEDNA's Experience:</strong>
Highlight 15+ years and 200+ successful projects
</div>
<div class="recommendation">
<strong>Security Focus:</strong>
Lead with SOC 2, ISO 27001 certifications
</div>
<div class="recommendation">
<strong>Methodology:</strong>
Detail 5-phase approach: Discovery → Design → Implementation → Testing → Deployment
</div>
<div class="recommendation">
<strong>Transparency:</strong>
Emphasize fixed-price model with milestone billing
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
<style>
.rfp-manager {
position: fixed;
top: 0;
right: -600px;
width: 600px;
height: 100vh;
background: #1a1a1a;
border-left: 1px solid #333;
transition: right 0.3s ease;
z-index: 1000;
overflow-y: auto;
}
.rfp-manager.visible {
right: 0;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid #333;
background: #2a2a2a;
}
.header h3 {
margin: 0;
color: #e0e0e0;
font-size: 1.2rem;
}
.close-btn {
background: none;
border: none;
color: #888;
font-size: 1.5rem;
cursor: pointer;
padding: 0.25rem;
}
.close-btn:hover {
color: #e0e0e0;
}
.content {
padding: 1rem;
}
.upload-section {
margin-bottom: 2rem;
}
.upload-section h4, .documents-section h4, .analysis-section h4 {
color: #4a9eff;
margin: 0 0 1rem 0;
font-size: 1rem;
}
.upload-area {
text-align: center;
padding: 1rem;
border: 2px dashed #333;
border-radius: 8px;
}
.upload-btn {
background: #4a9eff;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
.upload-btn:hover:not(:disabled) {
background: #357abd;
}
.upload-btn:disabled {
background: #666;
cursor: not-allowed;
}
.upload-help {
color: #888;
font-size: 0.8rem;
margin: 0;
}
.documents-list {
max-height: 300px;
overflow-y: auto;
}
.document-item {
display: flex;
align-items: center;
padding: 0.75rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
margin-bottom: 0.5rem;
}
.document-item:hover {
background: #2a2a2a;
}
.document-item.selected {
background: #1e3a5f;
border: 1px solid #4a9eff;
}
.doc-icon {
font-size: 1.5rem;
margin-right: 0.75rem;
}
.doc-info {
flex: 1;
}
.doc-name {
color: #e0e0e0;
font-weight: 500;
margin-bottom: 0.25rem;
}
.doc-meta {
color: #888;
font-size: 0.8rem;
}
.doc-status {
margin-left: 1rem;
}
.status-processed {
color: #4caf50;
font-size: 0.8rem;
}
.status-pending {
color: #ff9800;
font-size: 0.8rem;
}
.empty-state, .loading {
text-align: center;
color: #888;
padding: 2rem;
font-style: italic;
}
.analysis-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid #333;
}
.insights-grid {
display: grid;
gap: 1rem;
margin-bottom: 2rem;
}
.insight-card {
background: #2a2a2a;
padding: 1rem;
border-radius: 6px;
border: 1px solid #333;
}
.insight-card h5 {
margin: 0 0 0.75rem 0;
color: #4a9eff;
font-size: 0.9rem;
}
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
background: #333;
color: #e0e0e0;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
}
.tech-tag {
background: #1e3a5f;
color: #4a9eff;
}
.compliance-tag {
background: #1e4a1e;
color: #4caf50;
}
.timeline-tag {
background: #4a1e4a;
color: #e91e63;
}
.budget-tag {
background: #4a4a1e;
color: #ffeb3b;
}
.strategy-section {
background: #2a2a2a;
padding: 1rem;
border-radius: 6px;
border: 1px solid #333;
}
.strategy-section h5 {
margin: 0 0 1rem 0;
color: #4a9eff;
}
.recommendations {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.recommendation {
color: #e0e0e0;
font-size: 0.9rem;
line-height: 1.4;
}
.recommendation strong {
color: #4a9eff;
}
</style>