feat: parse text so better
This commit is contained in:
@@ -4,19 +4,51 @@ import com.approagency.drug.domain.model.*
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Evaluator
|
||||
|
||||
class DrugDetailParser {
|
||||
|
||||
fun parseDrugDetail(html: String): DrugDetail {
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
// اطلاعات پایه
|
||||
// تشخیص نوع صفحه: Generic (G) یا Brand (B)
|
||||
val isGenericPage = detectPageType(document)
|
||||
|
||||
return if (isGenericPage) {
|
||||
parseGenericPage(document)
|
||||
} else {
|
||||
parseBrandPage(document)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تشخیص نوع صفحه با بررسی URL canonical و ساختار DOM
|
||||
*/
|
||||
private fun detectPageType(document: Document): Boolean {
|
||||
// روش 1: بررسی URL canonical
|
||||
val canonicalUrl = document.select("link[rel=canonical]").attr("href")
|
||||
if (canonicalUrl.contains("/G-")) {
|
||||
return true
|
||||
}
|
||||
if (canonicalUrl.contains("/B-")) {
|
||||
return false
|
||||
}
|
||||
|
||||
// روش 2: بررسی ساختار DOM (Fallback)
|
||||
val hasGenericLayout = document.select("#UL_GenericTabInfo").isNotEmpty()
|
||||
val hasBrandLayout = document.select("#BrandInfoContainer").isNotEmpty()
|
||||
|
||||
return hasGenericLayout || !hasBrandLayout // Default to generic if ambiguous
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Generic Page Parsing
|
||||
// ============================================================
|
||||
private fun parseGenericPage(document: Document): DrugDetail {
|
||||
val genericId = extractGenericId(document)
|
||||
val persianName = extractPersianName(document)
|
||||
val englishName = extractEnglishName(document)
|
||||
|
||||
// استخراج تمام بخشها به صورت داینامیک
|
||||
val sections = extractAllSections(document)
|
||||
val sections = extractSectionsFromGeneric(document)
|
||||
|
||||
return DrugDetail(
|
||||
genericId = genericId,
|
||||
@@ -24,27 +56,30 @@ class DrugDetailParser {
|
||||
englishName = englishName,
|
||||
drugClass = extractDrugClass(document),
|
||||
therapeuticClass = extractTherapeuticClass(document),
|
||||
usage = sections["usage"] ?: extractSectionByText(document, "موارد مصرف"),
|
||||
mechanism = sections["mechanism"] ?: extractSectionByText(document, "مکانیسم اثر"),
|
||||
pharmacokinetics = sections["pharmacokinetics"] ?: extractSectionByText(document, "فارماکوکینتیک"),
|
||||
contraindications = sections["contraindications"] ?: extractSectionByText(document, "منع مصرف"),
|
||||
sideEffects = sections["sideEffects"] ?: extractSectionByText(document, "عوارض جانبی"),
|
||||
interactions = sections["interactions"] ?: extractSectionByText(document, "تداخلات دارویی"),
|
||||
warnings = sections["warnings"] ?: extractSectionByText(document, "هشدار"),
|
||||
recommendations = sections["recommendations"] ?: extractSectionByText(document, "توصیه"),
|
||||
usage = sections["usage"],
|
||||
mechanism = sections["mechanism"],
|
||||
pharmacokinetics = sections["pharmacokinetics"],
|
||||
contraindications = sections["contraindications"],
|
||||
sideEffects = sections["sideEffects"],
|
||||
interactions = sections["interactions"],
|
||||
warnings = sections["warnings"],
|
||||
recommendations = sections["recommendations"],
|
||||
pregnancyCategory = extractPregnancyCategory(document),
|
||||
pregnancyDescription = extractPregnancyDescription(document),
|
||||
dosageForms = extractDosageForms(document),
|
||||
brandNames = extractBrandNames(document),
|
||||
similarDrugs = extractSimilarDrugs(document),
|
||||
categories = extractCategories(document),
|
||||
comments = extractComments(document)
|
||||
comments = extractComments(document),
|
||||
manufacturer = null,
|
||||
isGeneric = true,
|
||||
generalInfo = extractGeneralInfoGeneric(document),
|
||||
specializedInfo = extractSpecializedInfoGeneric(document),
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractGenericId(document: Document): String {
|
||||
val urlElement = document.select("link[rel=canonical]").first()
|
||||
val url = urlElement?.attr("href") ?: ""
|
||||
val url = document.select("link[rel=canonical]").attr("href")
|
||||
val regex = "/G-(\\d+)/".toRegex()
|
||||
return regex.find(url)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
@@ -52,124 +87,283 @@ class DrugDetailParser {
|
||||
private fun extractPersianName(document: Document): String {
|
||||
val titleElement = document.select("h1.EnglishNumericFont").first()
|
||||
val fullText = titleElement?.text() ?: ""
|
||||
return fullText.replace("چیست و برای چه مواردی استفاده می شود؟", "").trim()
|
||||
// Remove the suffix "چیست و برای چه مواردی استفاده می شود؟"
|
||||
return fullText.replace(Regex("\\s*چیست و برای چه مواردی استفاده می شود\\?\\s*"), "").trim()
|
||||
}
|
||||
|
||||
private fun extractEnglishName(document: Document): String {
|
||||
val englishLabel = document.select("label.EnglishTopLabel").first()
|
||||
return englishLabel?.text()?.trim() ?: ""
|
||||
return document.select("label.EnglishTopLabel").text().trim()
|
||||
}
|
||||
|
||||
private fun extractDrugClass(document: Document): String? {
|
||||
val classElement = document.select("#divExtraInfo > div:first-child a.ahref_Generic").first()
|
||||
return classElement?.text()?.trim()
|
||||
return document.select("#divExtraInfo > div:first-child a.ahref_Generic").first()?.text()?.trim()
|
||||
}
|
||||
|
||||
private fun extractTherapeuticClass(document: Document): String? {
|
||||
val therapeuticElement = document.select("#divExtraInfo > div:last-child a.ahref_Generic").first()
|
||||
return therapeuticElement?.text()?.trim()
|
||||
// The therapeutic class can be a chain of links
|
||||
val classLinks = document.select("#divExtraInfo > div:last-child a.ahref_Generic")
|
||||
return if (classLinks.isNotEmpty()) classLinks.joinToString(" > ") { it.text().trim() } else null
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج بخشها با استفاده از ID (روش قبلی)
|
||||
* Generic pages store content in divs with IDs inside #EtelaatTakhasosiContent
|
||||
* The titles are h2.h2_TabTitle, and content is everything until the next h2.
|
||||
*/
|
||||
private fun extractAllSections(document: Document): Map<String, String> {
|
||||
private fun extractSectionsFromGeneric(document: Document): Map<String, String> {
|
||||
val sections = mutableMapOf<String, String>()
|
||||
val specializedContentDiv = document.getElementById("EtelaatTakhasosiContent") ?: return sections
|
||||
|
||||
// نقشه ID به کلید
|
||||
val idToKey = mapOf(
|
||||
"0" to "usage",
|
||||
"1" to "mechanism",
|
||||
"2" to "pharmacokinetics",
|
||||
"3" to "contraindications",
|
||||
"4" to "sideEffects",
|
||||
"5" to "interactions",
|
||||
"6" to "warnings",
|
||||
"7" to "recommendations"
|
||||
// Mapping of section title keywords to our data class keys
|
||||
val titleToKey = mapOf(
|
||||
"موارد مصرف" to "usage",
|
||||
"مکانیسم اثر" to "mechanism",
|
||||
"فارماکوکینتیک" to "pharmacokinetics",
|
||||
"منع مصرف" to "contraindications",
|
||||
"عوارض جانبی" to "sideEffects",
|
||||
"تداخلات دارویی" to "interactions",
|
||||
"هشدار" to "warnings",
|
||||
"توصیه های دارویی" to "recommendations"
|
||||
)
|
||||
|
||||
for ((id, key) in idToKey) {
|
||||
val section = extractSectionById(document, id)
|
||||
if (!section.isNullOrBlank()) {
|
||||
sections[key] = section
|
||||
val sectionHeaders = specializedContentDiv.select("h2.h2_TabTitle")
|
||||
|
||||
for (header in sectionHeaders) {
|
||||
val headerText = header.text().trim()
|
||||
val sectionKey = titleToKey.entries.find { headerText.contains(it.key) }?.value
|
||||
|
||||
if (sectionKey != null) {
|
||||
var contentElement = header.nextElementSibling()
|
||||
val contentBuilder = StringBuilder()
|
||||
|
||||
while (contentElement != null && !contentElement.select("h2.h2_TabTitle").hasText()) {
|
||||
// Extract clean text from various elements
|
||||
val text = cleanText(contentElement)
|
||||
if (text.isNotBlank()) {
|
||||
contentBuilder.append(text).append("\n\n")
|
||||
}
|
||||
contentElement = contentElement.nextElementSibling()
|
||||
}
|
||||
|
||||
val content = contentBuilder.toString().trim()
|
||||
if (content.isNotEmpty()) {
|
||||
sections[sectionKey] = content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
private fun extractSectionById(document: Document, sectionId: String): String? {
|
||||
val sectionElement = document.select("h2.h2_TabTitle#${sectionId}").first()
|
||||
if (sectionElement == null) return null
|
||||
/**
|
||||
* General info for generic pages is inside #EtelaatOmomiVaTakhasosi > #EtelaatOmomi
|
||||
*/
|
||||
private fun extractGeneralInfoGeneric(document: Document): String? {
|
||||
val generalDiv = document.select("#EtelaatOmomiVaTakhasosi #EtelaatOmomi").first() ?: return null
|
||||
// Clone the element to avoid modifying the original document
|
||||
val clone = generalDiv.clone()
|
||||
|
||||
val content = StringBuilder()
|
||||
var nextElement = sectionElement.nextElementSibling()
|
||||
// Remove the accordion (table of contents) as it's not part of the main content
|
||||
clone.select(".accordion").remove()
|
||||
// Remove navigation boxes if any
|
||||
clone.select("#nav_box_general").remove()
|
||||
|
||||
while (nextElement != null && !nextElement.select("h2.h2_TabTitle").hasText()) {
|
||||
if (nextElement.tagName() == "p" || nextElement.tagName() == "div") {
|
||||
val text = cleanHtmlText(nextElement.text())
|
||||
if (text.isNotBlank()) {
|
||||
content.append(text).append("\n\n")
|
||||
}
|
||||
}
|
||||
nextElement = nextElement.nextElementSibling()
|
||||
}
|
||||
|
||||
return content.toString().trim().takeIf { it.isNotEmpty() }
|
||||
return cleanText(clone).trim().takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
/**
|
||||
* استخراج بخش با جستجوی متن عنوان (روش جایگزین)
|
||||
* Specialized info for generic pages is inside #EtelaatTakhasosiContent
|
||||
*/
|
||||
private fun extractSectionByText(document: Document, titleKeyword: String): String? {
|
||||
// جستجوی هدر حاوی کلمه کلیدی
|
||||
val header = document.select("h2.h2_TabTitle, h3").firstOrNull {
|
||||
it.text().contains(titleKeyword, ignoreCase = true)
|
||||
} ?: return null
|
||||
private fun extractSpecializedInfoGeneric(document: Document): String? {
|
||||
val specializedDiv = document.getElementById("EtelaatTakhasosiContent") ?: return null
|
||||
val clone = specializedDiv.clone()
|
||||
|
||||
val content = StringBuilder()
|
||||
var nextElement = header.nextElementSibling()
|
||||
// Remove the table of contents
|
||||
clone.select(".accordion").remove()
|
||||
// Remove the "دارو های هم گروه" section and sources if you don't want them in specializedInfo
|
||||
clone.select("#Teammate").remove()
|
||||
clone.select("#externalLinks").remove()
|
||||
|
||||
while (nextElement != null && !nextElement.select("h2.h2_TabTitle, h3").hasText()) {
|
||||
if (nextElement.tagName() == "p" || nextElement.tagName() == "div") {
|
||||
val text = cleanHtmlText(nextElement.text())
|
||||
return cleanText(clone).trim().takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Brand Page Parsing
|
||||
// ============================================================
|
||||
private fun parseBrandPage(document: Document): DrugDetail {
|
||||
val brandId = extractBrandId(document)
|
||||
val persianName = extractBrandPersianName(document)
|
||||
val englishName = extractBrandEnglishName(document)
|
||||
val manufacturer = extractManufacturer(document)
|
||||
val genericInfo = extractGenericInfo(document)
|
||||
|
||||
// Extract all sections from .brandAttrPersDesc
|
||||
val (introText, sections) = extractBrandSections(document)
|
||||
|
||||
return DrugDetail(
|
||||
genericId = genericInfo?.genericId ?: "",
|
||||
persianName = persianName,
|
||||
englishName = englishName,
|
||||
drugClass = genericInfo?.persianName, // Drug class is essentially the generic name
|
||||
therapeuticClass = extractBrandTherapeuticClass(document),
|
||||
usage = sections["usage"] ?: introText,
|
||||
mechanism = sections["mechanism"],
|
||||
pharmacokinetics = null,
|
||||
contraindications = sections["contraindications"],
|
||||
sideEffects = sections["sideEffects"],
|
||||
interactions = sections["interactions"],
|
||||
warnings = sections["warnings"],
|
||||
recommendations = sections["recommendations"],
|
||||
pregnancyCategory = null,
|
||||
pregnancyDescription = null,
|
||||
dosageForms = extractOtherBrandForms(document),
|
||||
brandNames = listOf(), // This page itself is a brand
|
||||
similarDrugs = emptyList(),
|
||||
categories = null,
|
||||
comments = extractComments(document),
|
||||
manufacturer = manufacturer,
|
||||
genericInfo = genericInfo,
|
||||
otherBrandForms = extractOtherBrandForms(document),
|
||||
isGeneric = false
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractBrandId(document: Document): String {
|
||||
val url = document.select("link[rel=canonical]").attr("href")
|
||||
val regex = "/B-(\\d+)/".toRegex()
|
||||
return regex.find(url)?.groupValues?.get(1) ?: ""
|
||||
}
|
||||
|
||||
private fun extractBrandPersianName(document: Document): String {
|
||||
return document.select("#h1PersianName").text().trim()
|
||||
}
|
||||
|
||||
private fun extractBrandEnglishName(document: Document): String {
|
||||
return document.select("#h2EnglishName").text().trim()
|
||||
}
|
||||
|
||||
private fun extractManufacturer(document: Document): String? {
|
||||
val manufacturerLink = document.select("#divProducer a.ahref_Generic").first()
|
||||
return manufacturerLink?.text()?.trim()
|
||||
}
|
||||
|
||||
private fun extractGenericInfo(document: Document): GenericInfo? {
|
||||
val genericLink = document.select("#divAjzaContent a.ahref_Generic").first() ?: return null
|
||||
val href = genericLink.attr("href")
|
||||
val genericIdRegex = "/G-(\\d+)/".toRegex()
|
||||
val genericId = genericIdRegex.find(href)?.groupValues?.get(1) ?: ""
|
||||
|
||||
return GenericInfo(
|
||||
genericId = genericId,
|
||||
persianName = genericLink.text().trim(),
|
||||
detailUrl = "https://www.darooyab.ir$href"
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractBrandTherapeuticClass(document: Document): String? {
|
||||
val therapeuticContainer = document.select("#brand_desc > div:last-child").first()
|
||||
val text = therapeuticContainer?.text() ?: ""
|
||||
val regex = "طبقه بندی درمانی :\\s*(.+?)(?:\$|\\n)".toRegex()
|
||||
return regex.find(text)?.groupValues?.get(1)?.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all content from .brandAttrPersDesc.
|
||||
* Returns a Pair: first is the introductory text (before any h2), second is a map of section title to content.
|
||||
*/
|
||||
private fun extractBrandSections(document: Document): Pair<String?, Map<String, String>> {
|
||||
val container = document.select(".brandAttrPersDesc").first() ?: return Pair(null, emptyMap())
|
||||
val sections = mutableMapOf<String, String>()
|
||||
val titleToKey = mapOf(
|
||||
"موارد مصرف" to "usage",
|
||||
"مکانیسم اثر" to "mechanism",
|
||||
"منع مصرف" to "contraindications",
|
||||
"عوارض جانبی" to "sideEffects",
|
||||
"تداخلات دارویی" to "interactions",
|
||||
"هشدار" to "warnings",
|
||||
"توصیه های دارویی" to "recommendations"
|
||||
)
|
||||
|
||||
var introText: String? = null
|
||||
var currentSectionKey: String? = null
|
||||
val contentBuilder = StringBuilder()
|
||||
|
||||
for (child in container.children()) {
|
||||
if (child.tagName() == "h2" || child.tagName() == "h3") {
|
||||
// If we were building a previous section, save it
|
||||
if (currentSectionKey != null && contentBuilder.isNotEmpty()) {
|
||||
sections[currentSectionKey] = contentBuilder.toString().trim()
|
||||
contentBuilder.clear()
|
||||
}
|
||||
|
||||
// Start a new section
|
||||
val headerText = child.text().trim()
|
||||
currentSectionKey = titleToKey.entries.find { headerText.contains(it.key) }?.value
|
||||
} else {
|
||||
val text = cleanText(child)
|
||||
if (text.isNotBlank()) {
|
||||
content.append(text).append("\n\n")
|
||||
if (currentSectionKey == null) {
|
||||
// This is introductory text before any h2
|
||||
introText = introText?.let { "$it\n\n$text" } ?: text
|
||||
} else {
|
||||
contentBuilder.append(text).append("\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
nextElement = nextElement.nextElementSibling()
|
||||
}
|
||||
|
||||
return content.toString().trim().takeIf { it.isNotEmpty() }
|
||||
// Save the last section if any
|
||||
if (currentSectionKey != null && contentBuilder.isNotEmpty()) {
|
||||
sections[currentSectionKey] = contentBuilder.toString().trim()
|
||||
}
|
||||
|
||||
private fun cleanHtmlText(text: String): String {
|
||||
return text
|
||||
.replace(Regex("\\s+"), " ")
|
||||
.trim()
|
||||
return Pair(introText, sections)
|
||||
}
|
||||
|
||||
private fun extractOtherBrandForms(document: Document): List<DosageForm> {
|
||||
val forms = mutableListOf<DosageForm>()
|
||||
val brandmateDiv = document.select(".brandmate").first() ?: return forms
|
||||
|
||||
val links = brandmateDiv.select("a.ahref_Brand")
|
||||
for (link in links) {
|
||||
val persianName = link.text().trim()
|
||||
val detailUrl = "https://www.darooyab.ir${link.attr("href")}"
|
||||
|
||||
forms.add(
|
||||
DosageForm(
|
||||
code = "",
|
||||
persianName = persianName,
|
||||
englishName = "",
|
||||
isHighRisk = false,
|
||||
temperature = null,
|
||||
isVital = false,
|
||||
warningLabel = null,
|
||||
detailUrl = detailUrl
|
||||
)
|
||||
)
|
||||
}
|
||||
return forms
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Common Extractors (for both page types)
|
||||
// ============================================================
|
||||
|
||||
private fun extractPregnancyCategory(document: Document): String? {
|
||||
// On generic pages only
|
||||
val categoryElement = document.select("#UseInPregnancy .EnglishNumericFont, #UseInPregnancy > div.EnglishNumericFont").first()
|
||||
return categoryElement?.text()?.trim()
|
||||
}
|
||||
|
||||
private fun extractPregnancyDescription(document: Document): String? {
|
||||
// On generic pages only
|
||||
val descElement = document.select("#UseInPregnancy p, #UseInPregnancy .alert").first()
|
||||
val text = descElement?.text()?.trim()
|
||||
// اگر متن "مصرف در بارداری ثبت نشده است" باشد، null برگردان
|
||||
return if (text.isNullOrBlank() || text.contains("ثبت نشده")) null else text
|
||||
}
|
||||
|
||||
private fun extractDosageForms(document: Document): List<DosageForm> {
|
||||
val forms = mutableListOf<DosageForm>()
|
||||
|
||||
// بررسی وجود جدول اشکال دارویی
|
||||
val table = document.select("#TBL_AshkalDarooyi").first()
|
||||
if (table == null) {
|
||||
println("No dosage forms table found")
|
||||
return forms
|
||||
}
|
||||
val table = document.select("#TBL_AshkalDarooyi").first() ?: return forms
|
||||
|
||||
val rows = table.select("tbody tr").filter { !it.hasClass("showMoreRow") && it.id() != "showMoreRow" }
|
||||
|
||||
@@ -178,17 +372,13 @@ class DrugDetailParser {
|
||||
val cells = row.select("td")
|
||||
if (cells.size >= 2) {
|
||||
val persianNameElement = cells[1].select("h3").first()
|
||||
val englishNameElement = cells[1].select("label.EnglishNumericFont").first()
|
||||
|
||||
// بررسی اینکه آیا داده معتبر است
|
||||
val persianName = persianNameElement?.text()?.trim()
|
||||
if (persianName.isNullOrBlank()) continue
|
||||
val persianName = persianNameElement?.text()?.trim() ?: continue
|
||||
|
||||
forms.add(
|
||||
DosageForm(
|
||||
code = cells[0].text().trim(),
|
||||
persianName = persianName,
|
||||
englishName = englishNameElement?.text()?.trim() ?: "",
|
||||
englishName = cells[1].select("label.EnglishNumericFont").first()?.text()?.trim() ?: "",
|
||||
isHighRisk = cells.getOrNull(2)?.hasText() == true,
|
||||
temperature = cells.getOrNull(3)?.text()?.takeIf { it.isNotBlank() },
|
||||
isVital = cells.getOrNull(4)?.hasText() == true,
|
||||
@@ -200,34 +390,22 @@ class DrugDetailParser {
|
||||
println("Error parsing dosage form: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return forms
|
||||
}
|
||||
|
||||
private fun extractBrandNames(document: Document): List<BrandName> {
|
||||
val brands = mutableListOf<BrandName>()
|
||||
|
||||
// استخراج از بخش اسامی تجاری فارسی
|
||||
val persianRows = document.select("#PersCommertialDrugs .tableCommertial tbody tr.tr_persian")
|
||||
|
||||
// اگر ردیفی وجود نداشت، بررسی کن که آیا پیام "ثبت نشده" وجود دارد
|
||||
if (persianRows.isEmpty()) {
|
||||
val noDataMessage = document.select("#PersCommertialDrugs .alert")
|
||||
if (noDataMessage.isNotEmpty()) {
|
||||
println("No brand names available: ${noDataMessage.text()}")
|
||||
}
|
||||
return brands
|
||||
}
|
||||
if (persianRows.isEmpty()) return brands
|
||||
|
||||
for (row in persianRows) {
|
||||
try {
|
||||
val linkElement = row.select("td:first-child a.ahref_Generic").first()
|
||||
val persianName = linkElement?.text()?.trim() ?: continue
|
||||
val detailUrl = "https://www.darooyab.ir${linkElement.attr("href")}"
|
||||
|
||||
val manufacturerElement = row.select("td:eq(1) a.ahref_Generic").first()
|
||||
val manufacturer = manufacturerElement?.text()?.trim()
|
||||
|
||||
val importerElement = row.select("td:eq(2) a.ahref_Generic").first()
|
||||
val importer = importerElement?.text()?.trim()
|
||||
|
||||
@@ -244,19 +422,12 @@ class DrugDetailParser {
|
||||
println("Error parsing brand name: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return brands
|
||||
}
|
||||
|
||||
private fun extractSimilarDrugs(document: Document): List<SimilarDrug> {
|
||||
val drugs = mutableListOf<SimilarDrug>()
|
||||
|
||||
// بررسی وجود جدول داروهای هم گروه
|
||||
val table = document.select("table.tableGroups").first()
|
||||
if (table == null) {
|
||||
println("No similar drugs table found")
|
||||
return drugs
|
||||
}
|
||||
val table = document.select("table.tableGroups").first() ?: return drugs
|
||||
|
||||
val rows = table.select("tbody tr").filter {
|
||||
!it.hasClass("hidden-row") && it.select("a#toggleButton").isEmpty()
|
||||
@@ -264,8 +435,7 @@ class DrugDetailParser {
|
||||
|
||||
for (row in rows) {
|
||||
try {
|
||||
val cells = row.select("td")
|
||||
for (cell in cells) {
|
||||
for (cell in row.select("td")) {
|
||||
val link = cell.select("a.ahref_Generic").first()
|
||||
if (link != null && link.text().isNotBlank()) {
|
||||
val href = link.attr("href")
|
||||
@@ -286,7 +456,6 @@ class DrugDetailParser {
|
||||
println("Error parsing similar drug: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return drugs
|
||||
}
|
||||
|
||||
@@ -295,7 +464,6 @@ class DrugDetailParser {
|
||||
val martindale = martindaleLink?.text()?.trim()
|
||||
val martindaleUrl = martindaleLink?.attr("href")?.let { "https://www.darooyab.ir$it" }
|
||||
|
||||
// بررسی طبقه بندی درمانی (ممکن است "بدون طبقه بندی درمانی" باشد)
|
||||
val therapeuticLinks = document.select("#divExtraInfo > div:last-child a.ahref_Generic")
|
||||
val therapeutic = therapeuticLinks.mapNotNull { it.text().trim().takeIf { text ->
|
||||
text != "بدون طبقه بندی درمانی" && text.isNotBlank()
|
||||
@@ -320,15 +488,12 @@ class DrugDetailParser {
|
||||
try {
|
||||
val authorElement = element.select("span").first()
|
||||
val author = authorElement?.text()?.replace("(", "")?.replace(")", "")?.trim() ?: "ناشناس"
|
||||
|
||||
val date = authorElement?.text()?.let {
|
||||
val regex = "\\((\\d{4}/\\d{1,2}/\\d{1,2})\\)".toRegex()
|
||||
regex.find(it)?.groupValues?.get(1) ?: ""
|
||||
} ?: ""
|
||||
|
||||
val textElement = element.select("p.commentText").first()
|
||||
val text = textElement?.text()?.trim() ?: ""
|
||||
|
||||
if (text.isBlank()) continue
|
||||
|
||||
val responseElement = element.select(".responseComment").first()
|
||||
@@ -346,7 +511,6 @@ class DrugDetailParser {
|
||||
println("Error parsing comment: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return comments
|
||||
}
|
||||
|
||||
@@ -354,10 +518,8 @@ class DrugDetailParser {
|
||||
val doctorLink = element.select("a").first()
|
||||
val doctorName = doctorLink?.text()?.trim() ?: ""
|
||||
val doctorUrl = doctorLink?.attr("href")?.let { "https://www.darooyab.ir$it" }
|
||||
|
||||
val doctorText = element.select("span").first()?.text()?.trim() ?: ""
|
||||
val doctorTitle = doctorText.substringAfter(" - ").takeIf { it.isNotBlank() } ?: ""
|
||||
|
||||
val responseText = element.select("p.commentText").last()?.text()?.trim() ?: ""
|
||||
|
||||
return CommentResponse(
|
||||
@@ -367,4 +529,29 @@ class DrugDetailParser {
|
||||
doctorUrl = doctorUrl
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to clean and normalize text from an HTML element.
|
||||
* Converts block elements to newlines for better readability.
|
||||
*/
|
||||
private fun cleanText(element: Element): String {
|
||||
// Clone to avoid affecting the original
|
||||
val clone = element.clone()
|
||||
|
||||
// Replace block-level elements with newlines for structure
|
||||
clone.select("p, div, h2, h3, h4, li, br").before("\n")
|
||||
|
||||
// Get the text and clean it up
|
||||
var text = clone.text()
|
||||
.replace(Regex("\\n\\s*\\n+"), "\n\n") // Collapse multiple newlines
|
||||
.replace(Regex("[ \\t]+"), " ") // Collapse spaces/tabs
|
||||
.trim()
|
||||
|
||||
// Ensure lists look decent
|
||||
if (element.tagName() == "li") {
|
||||
text = "• $text"
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,19 @@ data class DrugDetail(
|
||||
val brandNames: List<BrandName>,
|
||||
val similarDrugs: List<SimilarDrug>,
|
||||
val categories: DrugCategories?,
|
||||
val comments: List<Comment>
|
||||
val comments: List<Comment>,
|
||||
val manufacturer: String? = null,
|
||||
val genericInfo: GenericInfo? = null,
|
||||
val otherBrandForms: List<DosageForm> = emptyList(),
|
||||
val isGeneric: Boolean = true,
|
||||
val generalInfo: String? = null,
|
||||
val specializedInfo: String? = null,
|
||||
)
|
||||
data class GenericInfo(
|
||||
val genericId: String,
|
||||
val persianName: String,
|
||||
val detailUrl: String
|
||||
)
|
||||
|
||||
data class DosageForm(
|
||||
val code: String,
|
||||
val persianName: String,
|
||||
@@ -30,7 +40,8 @@ data class DosageForm(
|
||||
val isHighRisk: Boolean,
|
||||
val temperature: String?,
|
||||
val isVital: Boolean,
|
||||
val warningLabel: String?
|
||||
val warningLabel: String?,
|
||||
val detailUrl: String? = null
|
||||
)
|
||||
|
||||
data class BrandName(
|
||||
|
||||
@@ -12,12 +12,10 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.FavoriteBorder
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@@ -36,7 +34,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -66,6 +63,7 @@ fun DrugDetailScreen(
|
||||
val dime = LocalDime.current
|
||||
val detailState by viewModel.detailState.collectAsStateWithLifecycle()
|
||||
var selectedTab by remember { mutableIntStateOf(0) }
|
||||
|
||||
val tabs = listOf("عمومی", "تخصصی", "اشکال دارویی", "اسامی تجاری")
|
||||
|
||||
LaunchedEffect(detailUrl) {
|
||||
@@ -80,16 +78,16 @@ fun DrugDetailScreen(
|
||||
is DrugDetailYabState.Success -> {
|
||||
Column {
|
||||
Text(
|
||||
text = state.drugDetail.persianName.take(20),
|
||||
text = state.drugDetail.persianName.take(25),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1
|
||||
)
|
||||
if (state.drugDetail.englishName.isNotEmpty()) {
|
||||
Text(
|
||||
text = state.drugDetail.englishName.take(25),
|
||||
fontSize = 12.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
text = state.drugDetail.englishName.take(30),
|
||||
fontSize = 11.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
@@ -120,9 +118,7 @@ fun DrugDetailScreen(
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
when (val state = detailState) {
|
||||
DrugDetailYabState.Idle -> {
|
||||
// Initial state
|
||||
}
|
||||
DrugDetailYabState.Idle -> {}
|
||||
|
||||
DrugDetailYabState.Loading -> {
|
||||
Box(
|
||||
@@ -144,9 +140,18 @@ fun DrugDetailScreen(
|
||||
}
|
||||
|
||||
is DrugDetailYabState.Success -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
val drugDetail = state.drugDetail
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
// Manufacturer Info Card (برای صفحات برند)
|
||||
if (!drugDetail.isGeneric && drugDetail.manufacturer != null) {
|
||||
ManufacturerInfoCard(
|
||||
manufacturer = drugDetail.manufacturer!!,
|
||||
genericInfo = drugDetail.genericInfo,
|
||||
modifier = Modifier.padding(horizontal = dime.md, vertical = dime.sm)
|
||||
)
|
||||
}
|
||||
|
||||
// Tab Row
|
||||
TabRow(
|
||||
selectedTabIndex = selectedTab,
|
||||
@@ -160,8 +165,9 @@ fun DrugDetailScreen(
|
||||
text = {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = if (selectedTab == index) FontWeight.Bold else FontWeight.Normal
|
||||
fontSize = 13.sp,
|
||||
fontWeight = if (selectedTab == index) FontWeight.Bold else FontWeight.Normal,
|
||||
maxLines = 1
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -178,16 +184,16 @@ fun DrugDetailScreen(
|
||||
) {
|
||||
when (selectedTab) {
|
||||
0 -> {
|
||||
item { GeneralInfoTabContent(state.drugDetail) }
|
||||
item { GeneralInfoTabContent(drugDetail) }
|
||||
}
|
||||
1 -> {
|
||||
item { SpecializedInfoTabContent(state.drugDetail) }
|
||||
item { SpecializedInfoTabContent(drugDetail) }
|
||||
}
|
||||
2 -> {
|
||||
item { DosageFormsTabContent(state.drugDetail.dosageForms) }
|
||||
item { DosageFormsTabContent(drugDetail) }
|
||||
}
|
||||
3 -> {
|
||||
item { BrandNamesTabContent(state.drugDetail.brandNames) }
|
||||
item { BrandNamesTabContent(drugDetail) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,11 +216,100 @@ fun DrugDetailScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ManufacturerInfoCard(
|
||||
manufacturer: String,
|
||||
genericInfo: com.approagency.drug.domain.model.GenericInfo?,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val dime = LocalDime.current
|
||||
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.15f)
|
||||
),
|
||||
shape = RoundedCornerShape(dime.sm)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(dime.md),
|
||||
verticalArrangement = Arrangement.spacedBy(dime.xs)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dime.xs)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = "سازنده:",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Text(
|
||||
text = manufacturer,
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
if (genericInfo != null) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(dime.xs)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = "ماده موثره:",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Text(
|
||||
text = genericInfo.persianName,
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// آپدیت GeneralInfoTabContent برای صفحات Generic
|
||||
@Composable
|
||||
fun GeneralInfoTabContent(drugDetail: DrugDetail) {
|
||||
val dime = LocalDime.current
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
|
||||
|
||||
if (drugDetail.isGeneric) {
|
||||
// ========== صفحات ژنریک (Generic) ==========
|
||||
|
||||
// اگر generalInfo وجود دارد (از EtelaatOmomiVaTakhasosi یا EtelaatOmomi)
|
||||
if (!drugDetail.generalInfo.isNullOrBlank()) {
|
||||
InfoSectionCard(
|
||||
title = "اطلاعات عمومی",
|
||||
content = drugDetail.generalInfo
|
||||
)
|
||||
}
|
||||
// اگر specializedInfo وجود دارد (از EtelaatTakhasosiContent)
|
||||
else if (!drugDetail.specializedInfo.isNullOrBlank()) {
|
||||
InfoSectionCard(
|
||||
title = "اطلاعات تخصصی",
|
||||
content = drugDetail.specializedInfo
|
||||
)
|
||||
}
|
||||
// در غیر این صورت، اطلاعات پراکنده را نمایش بده
|
||||
else {
|
||||
// Categories Card
|
||||
if (drugDetail.drugClass != null || drugDetail.therapeuticClass != null) {
|
||||
Card(
|
||||
@@ -233,11 +328,11 @@ fun GeneralInfoTabContent(drugDetail: DrugDetail) {
|
||||
Text(
|
||||
text = "طبقه بندی مارتیندل: ",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Text(
|
||||
text = it,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
@@ -247,11 +342,11 @@ fun GeneralInfoTabContent(drugDetail: DrugDetail) {
|
||||
Text(
|
||||
text = "طبقه درمانی: ",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp
|
||||
fontSize = 13.sp
|
||||
)
|
||||
Text(
|
||||
text = it,
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
@@ -276,20 +371,22 @@ fun GeneralInfoTabContent(drugDetail: DrugDetail) {
|
||||
Text(
|
||||
text = "مصرف در بارداری",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = Color(0xFFE65100)
|
||||
)
|
||||
drugDetail.pregnancyCategory?.let {
|
||||
Text(
|
||||
text = "گروه: $it",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
drugDetail.pregnancyDescription?.let {
|
||||
Spacer(modifier = Modifier.height(dime.xs))
|
||||
Text(
|
||||
text = it,
|
||||
fontSize = 13.sp,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 18.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
@@ -297,31 +394,109 @@ fun GeneralInfoTabContent(drugDetail: DrugDetail) {
|
||||
}
|
||||
}
|
||||
|
||||
// Usage Section
|
||||
// موارد مصرف
|
||||
drugDetail.usage?.let {
|
||||
InfoSectionCard(title = "موارد مصرف", content = it)
|
||||
}
|
||||
|
||||
// Contraindications Section
|
||||
// موارد منع مصرف
|
||||
drugDetail.contraindications?.let {
|
||||
InfoSectionCard(title = "موارد منع مصرف", content = it)
|
||||
}
|
||||
|
||||
// Side Effects Section
|
||||
// عوارض جانبی
|
||||
drugDetail.sideEffects?.let {
|
||||
InfoSectionCard(title = "عوارض جانبی", content = it)
|
||||
}
|
||||
|
||||
// Warnings Section
|
||||
// هشدارها
|
||||
drugDetail.warnings?.let {
|
||||
InfoSectionCard(title = "هشدارها", content = it)
|
||||
}
|
||||
|
||||
// توصیههای دارویی
|
||||
drugDetail.recommendations?.let {
|
||||
InfoSectionCard(title = "توصیههای دارویی", content = it)
|
||||
}
|
||||
}
|
||||
|
||||
// اگر هیچ اطلاعاتی وجود نداشت
|
||||
if (drugDetail.drugClass == null &&
|
||||
drugDetail.therapeuticClass == null &&
|
||||
drugDetail.pregnancyCategory == null &&
|
||||
drugDetail.usage == null &&
|
||||
drugDetail.contraindications == null &&
|
||||
drugDetail.sideEffects == null &&
|
||||
drugDetail.warnings == null &&
|
||||
drugDetail.generalInfo.isNullOrBlank() &&
|
||||
drugDetail.specializedInfo.isNullOrBlank()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "اطلاعات عمومی برای این دارو ثبت نشده است",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ========== صفحات برند (Brand) ==========
|
||||
// نمایش اطلاعات تخصصی برند که در بخش brandAttrPersDesc است
|
||||
drugDetail.usage?.let {
|
||||
InfoSectionCard(title = "موارد مصرف", content = it)
|
||||
}
|
||||
drugDetail.contraindications?.let {
|
||||
InfoSectionCard(title = "موارد منع مصرف", content = it)
|
||||
}
|
||||
drugDetail.sideEffects?.let {
|
||||
InfoSectionCard(title = "عوارض جانبی", content = it)
|
||||
}
|
||||
drugDetail.warnings?.let {
|
||||
InfoSectionCard(title = "هشدارها", content = it)
|
||||
}
|
||||
drugDetail.recommendations?.let {
|
||||
InfoSectionCard(title = "توصیههای دارویی", content = it)
|
||||
}
|
||||
|
||||
if (drugDetail.usage == null &&
|
||||
drugDetail.contraindications == null &&
|
||||
drugDetail.sideEffects == null &&
|
||||
drugDetail.warnings == null
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "اطلاعات عمومی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید.",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
val dime = LocalDime.current
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dime.md)) {
|
||||
|
||||
// برای صفحات برند، اطلاعات تخصصی معمولاً در قسمت brandAttrPersDesc است
|
||||
// که در پارسر ما به صورت usage, contraindications, sideEffects, warnings, recommendations ذخیره شده
|
||||
|
||||
drugDetail.usage?.let {
|
||||
InfoSectionCard(title = "موارد مصرف", content = it)
|
||||
}
|
||||
@@ -347,10 +522,12 @@ fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
|
||||
InfoSectionCard(title = "توصیههای دارویی", content = it)
|
||||
}
|
||||
|
||||
// اگر هیچ اطلاعات تخصصی وجود نداشت
|
||||
if (drugDetail.usage == null &&
|
||||
drugDetail.mechanism == null &&
|
||||
drugDetail.contraindications == null &&
|
||||
drugDetail.sideEffects == null
|
||||
drugDetail.sideEffects == null &&
|
||||
drugDetail.warnings == null
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@@ -359,8 +536,11 @@ fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "اطلاعات تخصصی برای این دارو ثبت نشده است",
|
||||
fontSize = 14.sp,
|
||||
text = if (drugDetail.isGeneric)
|
||||
"اطلاعات تخصصی برای این دارو ثبت نشده است"
|
||||
else
|
||||
"اطلاعات تخصصی برای این برند ثبت نشده است.\nبرای مشاهده اطلاعات کامل، به صفحه داروی ژنریک مراجعه کنید.",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
@@ -370,8 +550,9 @@ fun SpecializedInfoTabContent(drugDetail: DrugDetail) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DosageFormsTabContent(dosageForms: List<com.approagency.drug.domain.model.DosageForm>) {
|
||||
fun DosageFormsTabContent(drugDetail: DrugDetail) {
|
||||
val dime = LocalDime.current
|
||||
val dosageForms = drugDetail.dosageForms
|
||||
|
||||
if (dosageForms.isEmpty()) {
|
||||
Box(
|
||||
@@ -381,18 +562,32 @@ fun DosageFormsTabContent(dosageForms: List<com.approagency.drug.domain.model.Do
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "اشکال دارویی برای این دارو ثبت نشده است",
|
||||
fontSize = 14.sp,
|
||||
text = if (drugDetail.isGeneric)
|
||||
"اشکال دارویی برای این دارو ثبت نشده است"
|
||||
else
|
||||
"سایر اشکال دارویی این برند ثبت نشده است",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
|
||||
// اگر صفحه برند است، عنوان متفاوت نشان بده
|
||||
if (!drugDetail.isGeneric) {
|
||||
Text(
|
||||
text = "سایر محصولات این برند",
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(bottom = dime.xs)
|
||||
)
|
||||
}
|
||||
|
||||
dosageForms.forEach { form ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
shape = RoundedCornerShape(dime.sm)
|
||||
) {
|
||||
Column(
|
||||
@@ -401,55 +596,49 @@ fun DosageFormsTabContent(dosageForms: List<com.approagency.drug.domain.model.Do
|
||||
) {
|
||||
Text(
|
||||
text = form.persianName,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 15.sp
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
if (form.englishName.isNotBlank()) {
|
||||
Text(
|
||||
text = form.englishName,
|
||||
fontSize = 12.sp,
|
||||
fontSize = 11.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
|
||||
)
|
||||
}
|
||||
|
||||
// نشانگرهای خطر
|
||||
if (form.isHighRisk || form.isVital) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(dime.sm)
|
||||
horizontalArrangement = Arrangement.spacedBy(dime.xs)
|
||||
) {
|
||||
if (form.isHighRisk) {
|
||||
androidx.compose.material3.Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = Color(0xFFFFEBEE),
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
color = Color(0xFFFFEBEE)
|
||||
) {
|
||||
Text(
|
||||
text = "پرخطر",
|
||||
fontSize = 10.sp,
|
||||
color = Color(0xFFC62828),
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (form.isVital) {
|
||||
androidx.compose.material3.Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
color = Color(0xFFE8F5E9),
|
||||
modifier = Modifier.padding(vertical = 4.dp)
|
||||
color = Color(0xFFE8F5E9)
|
||||
) {
|
||||
Text(
|
||||
text = "حیاتی",
|
||||
fontSize = 10.sp,
|
||||
color = Color(0xFF2E7D32),
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!form.warningLabel.isNullOrBlank()) {
|
||||
Text(
|
||||
text = "⚠️ ${form.warningLabel}",
|
||||
fontSize = 11.sp,
|
||||
color = Color(0xFFE65100),
|
||||
modifier = Modifier.padding(top = dime.xs)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,10 +648,10 @@ fun DosageFormsTabContent(dosageForms: List<com.approagency.drug.domain.model.Do
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BrandNamesTabContent(brandNames: List<com.approagency.drug.domain.model.BrandName>) {
|
||||
fun BrandNamesTabContent(drugDetail: DrugDetail) {
|
||||
val dime = LocalDime.current
|
||||
|
||||
if (brandNames.isEmpty()) {
|
||||
if (drugDetail.isGeneric && drugDetail.brandNames.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -471,14 +660,14 @@ fun BrandNamesTabContent(brandNames: List<com.approagency.drug.domain.model.Bran
|
||||
) {
|
||||
Text(
|
||||
text = "اسامی تجاری برای این دارو ثبت نشده است",
|
||||
fontSize = 14.sp,
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
} else {
|
||||
} else if (drugDetail.isGeneric && drugDetail.brandNames.isNotEmpty()) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(dime.sm)) {
|
||||
brandNames.forEach { brand ->
|
||||
drugDetail.brandNames.forEach { brand ->
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
@@ -525,6 +714,21 @@ fun BrandNamesTabContent(brandNames: List<com.approagency.drug.domain.model.Bran
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// صفحه برند است - اسامی تجاری برای برند معنی ندارد
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "این صفحه مربوط به یک محصول تجاری است. برای مشاهده اسامی تجاری سایر برندها، به صفحه داروی ژنریک مراجعه کنید.",
|
||||
fontSize = 13.sp,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,14 +752,14 @@ fun InfoSectionCard(
|
||||
Text(
|
||||
text = title,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp,
|
||||
fontSize = 15.sp,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Divider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.2f))
|
||||
Text(
|
||||
text = content,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 22.sp,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 20.sp,
|
||||
textAlign = TextAlign.Justify,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.85f)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user