Docker config
This commit is contained in:
23
app/components/SampleInitiation/ContactNote.jsx
Normal file
23
app/components/SampleInitiation/ContactNote.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const ContactNote = () => {
|
||||
return (
|
||||
<div className="bg-white py-4">
|
||||
<div className="container">
|
||||
<p className="text-gray-700 leading-relaxed ml-6 md:ml-8 lg:ml-8">
|
||||
Please reach out to us by emailing{' '}
|
||||
<Link
|
||||
href="mailto:info@operifytech.com"
|
||||
className="text-blue-600 hover:text-blue-700 transition-colors"
|
||||
>
|
||||
info@operifytech.com
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContactNote;
|
||||
41
app/components/SampleInitiation/PageTitle.jsx
Normal file
41
app/components/SampleInitiation/PageTitle.jsx
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
|
||||
const PageTitle = () => {
|
||||
return (
|
||||
<section
|
||||
className="relative bg-cover bg-center py-4 sm:py-6 h-auto sm:h-32 md:h-40 lg:h-24 min-h-[120px] sm:min-h-[140px]"
|
||||
style={{ backgroundImage: "url('images/bredcrumb.jpg')" }}
|
||||
>
|
||||
{/* Breadcrumb */}
|
||||
<div className="relative z-10 mb-2 sm:mb-1 pt-2 sm:pt-0 sm:-mt-3 lg:-mt-3">
|
||||
<div className="container mx-auto max-w-none px-4">
|
||||
<nav className="flex flex-wrap items-center gap-1 sm:gap-2 text-xs sm:text-sm lg:text-sm">
|
||||
<a href="/" className="text-white hover:text-yellow-400 underline whitespace-nowrap">Home</a>
|
||||
<span className="text-white flex-shrink-0">
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<a href="/sample-submission-guideline" className="text-white hover:text-yellow-400 underline whitespace-nowrap">Knowledge Hub</a>
|
||||
<span className="text-white flex-shrink-0">
|
||||
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</span>
|
||||
<span className="text-white">Sample Initiation Form</span>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page Title */}
|
||||
<div className="relative z-10 text-center pb-2 sm:pb-0 sm:-mt-2 lg:mt-4">
|
||||
<h1 className="text-lg sm:text-2xl md:text-3xl lg:text-3xl xl:text-4xl font-bold text-white mb-2 px-4 leading-tight">
|
||||
Sample Initiation Form
|
||||
</h1>
|
||||
<div className="w-12 sm:w-14 md:w-16 lg:w-16 h-1 bg-yellow-400 mx-auto"></div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
35
app/components/SampleInitiation/ProcessSection.jsx
Normal file
35
app/components/SampleInitiation/ProcessSection.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import ProcessSteps from './ProcessSteps';
|
||||
import SubmissionOptions from './SubmissionOptions';
|
||||
|
||||
const ProcessSection = () => {
|
||||
return (
|
||||
<section className="bg-white">
|
||||
<div className="container mx-auto max-w-none px-4">
|
||||
<div className="bg-white p-4 md:p-6">
|
||||
{/* Main Title */}
|
||||
<div className="text-left mb-4">
|
||||
<h2 className="text-2xl md:text-4xl text-gray-600 font-normal">
|
||||
Welcome to Our Online Submission Portal!
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Two Column Layout */}
|
||||
<div className="flex flex-col lg:flex-row gap-8 lg:gap-20 items-start">
|
||||
{/* Left Column - Process Steps */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<ProcessSteps />
|
||||
</div>
|
||||
|
||||
{/* Right Column - Submission Options */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<SubmissionOptions />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessSection;
|
||||
61
app/components/SampleInitiation/ProcessSteps.jsx
Normal file
61
app/components/SampleInitiation/ProcessSteps.jsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
|
||||
const ProcessSteps = () => {
|
||||
const steps = [
|
||||
{
|
||||
number: 1,
|
||||
title: "Review Sample Requirements:",
|
||||
items: [
|
||||
<>Before submitting forms and samples, check the <a href="/sample-submission-guideline" className="text-gray-500 underline">sample submission guidelines</a> page and plan your project accordingly.</>,
|
||||
<>Read the <a href="/packaging-and-shipping-guideline" className="text-gray-500 underline">packaging and shipping</a> guidelines and fill out the appropriate sample Initiation form.</>,
|
||||
"Please note that samples will be processed in the order of date received physically in the lab along with duly filled Sample Submission Form. Submitting an online form without sample shipment will not reserve a space in the queue."
|
||||
]
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: "Match Sample Names",
|
||||
items: [
|
||||
"Ensure sample names match what is written on your tubes. Sample names must be between 4-6 characters, using uppercase letters, numbers, and underscores only."
|
||||
]
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
title: "Ship or Drop-Off Samples:",
|
||||
items: [
|
||||
<>Ship or drop off your samples at the lab with a printed copy of the submission form. We can also pick up the sample from your institution(additional logistic charges will be applicable). Check the <a href="/packaging-and-shipping-guideline#schedule-content" className="text-gray-500 underline">shipping schedule</a> for more details.</>
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{steps.map((step) => (
|
||||
<div key={step.number} className="flex items-start space-x-4">
|
||||
{/* Step Number */}
|
||||
<div
|
||||
className="flex-shrink-0 w-8 h-8 text-white rounded-md flex items-center justify-center text-base font-bold"
|
||||
style={{ backgroundColor: '#faae31' }}
|
||||
>
|
||||
{step.number}
|
||||
</div>
|
||||
|
||||
{/* Step Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-lg md:text-xl font-medium mb-2" style={{ color: '#2a6564' }}>
|
||||
{step.title}
|
||||
</h3>
|
||||
<ul className="list-disc list-inside space-y-2 text-gray-700 leading-relaxed pl-4">
|
||||
{step.items.map((item, index) => (
|
||||
<li key={index} className="text-justify">
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProcessSteps;
|
||||
16
app/components/SampleInitiation/SampleInitiationPage.jsx
Normal file
16
app/components/SampleInitiation/SampleInitiationPage.jsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import PageTitle from './PageTitle';
|
||||
import ProcessSection from './ProcessSection';
|
||||
import ContactNote from './ContactNote';
|
||||
|
||||
const SampleInitiationPage = () => {
|
||||
return (
|
||||
<div className="page-content">
|
||||
<PageTitle />
|
||||
<ProcessSection />
|
||||
<ContactNote />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SampleInitiationPage;
|
||||
260
app/components/SampleInitiation/SubmissionOptions.jsx
Normal file
260
app/components/SampleInitiation/SubmissionOptions.jsx
Normal file
@ -0,0 +1,260 @@
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
|
||||
const SubmissionOptions = () => {
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
|
||||
const handleFillManually = () => {
|
||||
window.location.href = '/samples-form';
|
||||
};
|
||||
|
||||
const handleFileUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const validTypes = ['.xlsx', '.xls'];
|
||||
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
|
||||
|
||||
if (validTypes.includes(fileExtension)) {
|
||||
processExcelFile(file);
|
||||
} else {
|
||||
alert('Please select a valid Excel file (.xlsx or .xls)');
|
||||
event.target.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processExcelFile = async (file) => {
|
||||
setIsProcessing(true);
|
||||
|
||||
try {
|
||||
// Import XLSX dynamically since it's a client-side library
|
||||
const XLSX = await import('xlsx');
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { defval: '' });
|
||||
|
||||
if (jsonData.length > 0) {
|
||||
// Store the Excel data in sessionStorage
|
||||
sessionStorage.setItem('excelData', JSON.stringify(jsonData));
|
||||
sessionStorage.setItem('uploadedFileName', file.name);
|
||||
|
||||
// Redirect to samples_form after a short delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '/samples-form';
|
||||
}, 1000);
|
||||
} else {
|
||||
alert('No valid data found in the Excel file.');
|
||||
setIsProcessing(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Excel processing error:', error);
|
||||
alert('Error processing Excel file. Please check the file format and try again.');
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
} catch (error) {
|
||||
console.error('Error loading XLSX library:', error);
|
||||
alert('Error loading file processor. Please try again.');
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadTemplate = async () => {
|
||||
try {
|
||||
// Import XLSX dynamically
|
||||
const XLSX = await import('xlsx');
|
||||
|
||||
// Create template data structure with only column headers
|
||||
const templateData = [
|
||||
{
|
||||
// Customer Information
|
||||
'Principal Investigator': '',
|
||||
'Email': '',
|
||||
'Company/Institution': '',
|
||||
'Contact Number': '',
|
||||
'Address': '',
|
||||
'City': '',
|
||||
'State': '',
|
||||
'Pin': '',
|
||||
'Secondary Contact': '',
|
||||
'Secondary Email': '',
|
||||
'Secondary Company/Institution': '',
|
||||
'Secondary Contact Number': '',
|
||||
|
||||
// Sample Information
|
||||
'Project Title': '',
|
||||
'Number of Samples': '',
|
||||
'Sample Type': '',
|
||||
'Sample Type Other': '',
|
||||
'Sample Source': '',
|
||||
'Sample Source Other': '',
|
||||
'Pathogenicity': '',
|
||||
'Sample Remarks': '',
|
||||
|
||||
// Service Information
|
||||
'Service Requested': '',
|
||||
'Service Requested Other': '',
|
||||
'Type of Library': '',
|
||||
'Type of Library Other': '',
|
||||
'Required Library Size': '',
|
||||
'Required Library Size Other': '',
|
||||
'Index Information': '',
|
||||
'Kit Information': '',
|
||||
'Sequencing Platform': '',
|
||||
'Sequencing Platform Other': '',
|
||||
'Sequencing Read Length': '',
|
||||
'Sequencing Read Length Other': '',
|
||||
'Total Data Requirement': '',
|
||||
'Service Remarks': '',
|
||||
|
||||
// Bioinformatics Information
|
||||
'Analysis Requested': '',
|
||||
'Analysis Details': '',
|
||||
'Reference Genome Available': '',
|
||||
'Genome Size': '',
|
||||
'Special Consideration': '',
|
||||
|
||||
// Sample Details
|
||||
'Serial Number': '',
|
||||
'Sample Name': '',
|
||||
'Storage Temp': '',
|
||||
'Preservative Reagent': '',
|
||||
'Temp Information': '',
|
||||
'Comments': ''
|
||||
}
|
||||
];
|
||||
|
||||
// Create workbook and worksheet
|
||||
const workbook = XLSX.utils.book_new();
|
||||
const worksheet = XLSX.utils.json_to_sheet(templateData);
|
||||
|
||||
// Set column widths for better readability
|
||||
const colWidths = Object.keys(templateData[0]).map(() => ({ wch: 20 }));
|
||||
worksheet['!cols'] = colWidths;
|
||||
|
||||
// Add worksheet to workbook
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sample Initiation Form');
|
||||
|
||||
// Generate Excel file and download
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const filename = `Sample_Initiation_Form_Template_${today}.xlsx`;
|
||||
XLSX.writeFile(workbook, filename);
|
||||
|
||||
// Show success message
|
||||
showMessage('Excel template downloaded successfully!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Error downloading template:', error);
|
||||
alert('Error downloading template. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const showMessage = (message, type) => {
|
||||
// Create and show a toast message
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `fixed top-5 right-5 z-50 max-w-sm p-4 rounded-lg shadow-lg font-medium ${
|
||||
type === 'success'
|
||||
? 'bg-green-100 text-green-800 border border-green-200'
|
||||
: 'bg-red-100 text-red-800 border border-red-200'
|
||||
}`;
|
||||
messageDiv.textContent = message;
|
||||
|
||||
document.body.appendChild(messageDiv);
|
||||
|
||||
// Auto remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (messageDiv.parentNode) {
|
||||
messageDiv.remove();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Title */}
|
||||
<div>
|
||||
<h3 className="text-gray-600 text-lg md:text-xl font-medium mb-6">
|
||||
Based on the convenience, please select one option:
|
||||
</h3>
|
||||
|
||||
{/* Process Image */}
|
||||
<div className="text-center my-6">
|
||||
<Image
|
||||
src="/images/sample-process-steps1.png"
|
||||
alt="Sample Submission Process"
|
||||
width={500}
|
||||
height={300}
|
||||
className="max-w-full h-auto rounded-lg mx-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="space-y-2">
|
||||
{/* Main Action Buttons */}
|
||||
<div className="flex flex-col md:flex-row gap-4 items-center justify-center">
|
||||
<button
|
||||
onClick={handleFillManually}
|
||||
className="w-full md:w-auto px-6 py-3 bg-teal-600 text-white font-medium rounded hover:bg-teal-700 transition-colors"
|
||||
>
|
||||
Fill Manually
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => document.getElementById('fileUpload').click()}
|
||||
className="w-full md:w-auto px-6 py-3 bg-transparent border border-gray-300 text-gray-600 font-normal rounded hover:bg-gray-50 transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<Image
|
||||
src="/images/file-icon.svg"
|
||||
alt="File Icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Hidden File Input */}
|
||||
<input
|
||||
type="file"
|
||||
id="fileUpload"
|
||||
className="hidden"
|
||||
accept=".xlsx,.xls"
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
|
||||
{/* Processing Indicator */}
|
||||
{isProcessing && (
|
||||
<div className="text-center py-4">
|
||||
<div className="inline-block w-8 h-8 border-4 border-gray-200 border-t-teal-600 rounded-full animate-spin mb-2"></div>
|
||||
<p className="text-gray-600">Processing Excel file and redirecting...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Or Divider */}
|
||||
<div className="text-center text-gray-500 text-base">
|
||||
or
|
||||
</div>
|
||||
|
||||
{/* Download Template Button */}
|
||||
<div className="text-center">
|
||||
<button
|
||||
onClick={handleDownloadTemplate}
|
||||
className="text-teal-600 text-base underline hover:text-teal-700 transition-colors bg-transparent border-none cursor-pointer"
|
||||
>
|
||||
Download form as Excel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmissionOptions;
|
||||
Reference in New Issue
Block a user