Refactor: clients → companies schema v2

This commit is contained in:
Krao Hasanee
2026-03-26 23:42:06 -04:00
commit 719209fa25
61 changed files with 8192 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
import { useState } from 'react';
const MAX_FILES = 20;
const MAX_SIZE_MB = 10;
const MAX_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024;
const formatSize = (bytes) => {
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
export default function FileAttachment({ files, onChange }) {
const [errors, setErrors] = useState([]);
const handleChange = (e) => {
const incoming = Array.from(e.target.files);
const combined = [...files, ...incoming];
const errs = [];
if (combined.length > MAX_FILES) {
errs.push(`Maximum ${MAX_FILES} files allowed.`);
}
incoming.filter(f => f.size > MAX_SIZE_BYTES)
.forEach(f => errs.push(`"${f.name}" exceeds ${MAX_SIZE_MB} MB limit.`));
if (errs.length > 0) { setErrors(errs); return; }
setErrors([]);
onChange(combined);
e.target.value = '';
};
const remove = (index) => {
setErrors([]);
onChange(files.filter((_, i) => i !== index));
};
return (
<div className="form-group">
<label>
Attach Files
<span style={{ fontWeight: 400, color: 'var(--text-muted)', marginLeft: 6 }}>
Up to {MAX_FILES} files · Max {MAX_SIZE_MB} MB each · Any file type
</span>
</label>
<div style={{
border: `2px dashed ${files.length > 0 ? 'var(--accent)' : 'var(--border)'}`,
borderRadius: 8, padding: '18px 16px', textAlign: 'center',
background: files.length > 0 ? '#fffbeb' : '#fafafa', transition: 'all 0.15s',
}}>
<input type="file" multiple onChange={handleChange} style={{ display: 'none' }} id="req-file-upload" />
<label htmlFor="req-file-upload" style={{ cursor: 'pointer' }}>
<div style={{ fontSize: 22, marginBottom: 4 }}>📎</div>
<div style={{ fontWeight: 600, fontSize: 13, color: 'var(--text-primary)' }}>
{files.length > 0 ? `${files.length} file${files.length !== 1 ? 's' : ''} attached — click to add more` : 'Click to attach files'}
</div>
<div style={{ fontSize: 12, color: 'var(--text-secondary)', marginTop: 2 }}>Any file type accepted</div>
</label>
</div>
{errors.length > 0 && errors.map((err, i) => (
<div key={i} style={{ fontSize: 12, color: 'var(--danger)', marginTop: 6 }}> {err}</div>
))}
{files.length > 0 && (
<div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
{files.map((file, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '7px 12px', background: 'white', borderRadius: 8, border: '1px solid var(--border)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span>📄</span>
<div>
<div style={{ fontSize: 13, fontWeight: 600 }}>{file.name}</div>
<div style={{ fontSize: 11, color: 'var(--text-muted)' }}>{formatSize(file.size)}</div>
</div>
</div>
<button type="button" onClick={() => remove(i)}
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--danger)', fontSize: 16, padding: '0 4px' }}>
</button>
</div>
))}
<div style={{ fontSize: 11, color: 'var(--text-muted)', textAlign: 'right' }}>
{files.length}/{MAX_FILES} files
</div>
</div>
)}
</div>
);
}