edit for portal & tender

This commit is contained in:
Aya
2026-06-03 13:01:51 +03:00
parent 61ca570e7a
commit 96386887fb
17 changed files with 1280 additions and 147 deletions

View File

@@ -87,10 +87,13 @@ function TenderDetailContent() {
const [completeNotes, setCompleteNotes] = useState('')
const [directiveTypeValues, setDirectiveTypeValues] = useState<string[]>([])
const [submitting, setSubmitting] = useState(false)
const fileInputRef = useRef<HTMLInputElement>(null)
const directiveFileInputRef = useRef<HTMLInputElement>(null)
const [uploadingDirectiveId, setUploadingDirectiveId] = useState<string | null>(null)
const [directiveIdForUpload, setDirectiveIdForUpload] = useState<string | null>(null)
const [uploadingCategory, setUploadingCategory] = useState<string | null>(null)
const termsInputRef = useRef<HTMLInputElement>(null)
const costInputRef = useRef<HTMLInputElement>(null)
const offersInputRef = useRef<HTMLInputElement>(null)
const fetchTender = async () => {
try {
@@ -213,11 +216,15 @@ function TenderDetailContent() {
}
}
const handleTenderFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const handleTenderFileUpload = async (
e: React.ChangeEvent<HTMLInputElement>,
category?: string,
) => {
const files = Array.from(e.target.files || [])
if (!files.length) return
setSubmitting(true)
if (category) setUploadingCategory(category)
else setSubmitting(true)
let successCount = 0
let failCount = 0
@@ -225,7 +232,7 @@ function TenderDetailContent() {
// Upload files sequentially so a failure of one file doesn't break the rest.
for (const file of files) {
try {
await tendersAPI.uploadTenderAttachment(tenderId, file)
await tendersAPI.uploadTenderAttachment(tenderId, file, category)
successCount++
} catch (err: any) {
failCount++
@@ -244,6 +251,7 @@ function TenderDetailContent() {
if (successCount > 0) fetchTender()
} finally {
setSubmitting(false)
setUploadingCategory(null)
e.target.value = ''
}
}
@@ -525,66 +533,102 @@ function TenderDetailContent() {
{activeTab === 'attachments' && (
<div>
<div className="flex items-center gap-4 mb-4">
<input
type="file"
ref={fileInputRef}
multiple
className="hidden"
onChange={handleTenderFileUpload}
/>
<button
onClick={() => fileInputRef.current?.click()}
disabled={submitting}
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50"
>
{submitting ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Upload className="h-4 w-4" />
)}
{t('tenders.uploadFile')}
</button>
</div>
{(() => {
const all = (tender.attachments || []) as any[]
const sections: Array<{
key: string
label: string
category: string
ref: React.RefObject<HTMLInputElement>
}> = [
{ key: 'terms', label: 'دفتر الشروط', category: 'TERMS_BOOKLET', ref: termsInputRef },
{ key: 'cost', label: 'cost sheet ', category: 'COST_SHEET', ref: costInputRef },
{ key: 'offers', label: 'proposal', category: 'OFFERS', ref: offersInputRef },
]
{!tender.attachments?.length ? (
<p className="text-gray-500">{t('common.noData')}</p>
) : (
<ul className="space-y-2">
{tender.attachments.map((a: any) => (
<li
key={a.id}
className="flex items-center justify-between border rounded px-3 py-2"
>
<a
href={`${process.env.NEXT_PUBLIC_API_URL}/tenders/attachments/${a.id}/view`}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-indigo-600 hover:underline flex items-center gap-1"
>
<ExternalLink className="h-4 w-4" />
{getDisplayFileName(a)}
</a>
// Legacy attachments without a recognized category live under
// the dafter section by default so nothing gets hidden.
const knownCategories = new Set(sections.map((s) => s.category))
const inSection = (a: any, category: string) =>
a.category === category ||
(category === 'TERMS_BOOKLET' && (!a.category || !knownCategories.has(a.category)))
<button
onClick={async () => {
if (!confirm('حذف الملف؟')) return
try {
await tendersAPI.deleteAttachment(a.id)
toast.success('تم الحذف')
fetchTender()
} catch {
toast.error('فشل الحذف')
}
}}
className="text-red-600 text-sm hover:underline"
>
حذف
</button>
</li>
))}
</ul>
)}
return (
<div className="space-y-6">
{sections.map((section) => {
const items = all.filter((a) => inSection(a, section.category))
const isUploading = uploadingCategory === section.category
return (
<div key={section.key} className="border rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-base font-semibold text-gray-900">{section.label}</h3>
<div>
<input
type="file"
ref={section.ref}
multiple
className="hidden"
onChange={(e) => handleTenderFileUpload(e, section.category)}
/>
<button
type="button"
onClick={() => section.ref.current?.click()}
disabled={isUploading}
className="flex items-center gap-2 px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50 text-sm"
>
{isUploading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Upload className="h-4 w-4" />
)}
{t('tenders.uploadFile')}
</button>
</div>
</div>
{items.length === 0 ? (
<p className="text-gray-500 text-sm">{t('common.noData')}</p>
) : (
<ul className="space-y-2">
{items.map((a: any) => (
<li
key={a.id}
className="flex items-center justify-between border rounded px-3 py-2"
>
<a
href={`${process.env.NEXT_PUBLIC_API_URL}/tenders/attachments/${a.id}/view`}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-indigo-600 hover:underline flex items-center gap-1"
>
<ExternalLink className="h-4 w-4" />
{getDisplayFileName(a)}
</a>
<button
onClick={async () => {
if (!confirm('حذف الملف؟')) return
try {
await tendersAPI.deleteAttachment(a.id)
toast.success('تم الحذف')
fetchTender()
} catch {
toast.error('فشل الحذف')
}
}}
className="text-red-600 text-sm hover:underline"
>
حذف
</button>
</li>
))}
</ul>
)}
</div>
)
})}
</div>
)
})()}
</div>
)}