diff --git a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/ReaderItem.tsx b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/ReaderItem.tsx index ac3eea183d..ce20297c4e 100644 --- a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/ReaderItem.tsx +++ b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/ReaderItem.tsx @@ -10,6 +10,7 @@ interface ReaderItemProps { reader: QiraatReader; transmitters: QiraatTransmitter[]; readings: QiraatReading[]; + readerColor?: string; onInfoClick?: () => void; onTransmitterClick?: (transmitterId: number) => void; isClickable?: boolean; @@ -26,6 +27,7 @@ const ReaderItem: React.FC = ({ reader, transmitters, readings, + readerColor, onInfoClick, onTransmitterClick, isClickable = false, @@ -57,12 +59,14 @@ const ReaderItem: React.FC = ({ if (transmitterReading) return transmitterReading.color || DEFAULT_COLOR; - // 2. If not found directly, the transmitter inherits color from its parent reader - // Find a reading where this reader appears in the matrix + // 2. If the parent component has pre-computed a color for this reader, use it + if (readerColor) return readerColor; + + // 3. If not found directly, the transmitter inherits color from its parent reader const readerReading = readings.find(({ matrix }) => matrix?.readers?.includes(reader.id)); if (readerReading) return readerReading.color || DEFAULT_COLOR; - // 3. Default fallback - no association found + // 4. Default fallback - no association found return DEFAULT_COLOR; }; diff --git a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/index.tsx b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/index.tsx index ee4a03508f..eea6eddf17 100644 --- a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/index.tsx +++ b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/ReadersPanel/index.tsx @@ -16,6 +16,7 @@ interface ReadersPanelProps { readers: QiraatReader[]; transmitters: QiraatTransmitter[]; readings: QiraatReading[]; + readerColorMap: Map; isExpanded: boolean; onToggleExpand: () => void; onTransmitterClick?: (transmitterId: number) => void; @@ -32,6 +33,7 @@ const ReadersPanel: React.FC = ({ readers, transmitters, readings, + readerColorMap, isExpanded, onToggleExpand, onTransmitterClick, @@ -101,6 +103,7 @@ const ReadersPanel: React.FC = ({ reader={reader} transmitters={transmitters} readings={readings} + readerColor={readerColorMap.get(reader.id)} onInfoClick={() => onReaderInfoClick?.(reader.id)} onTransmitterClick={onTransmitterClick} isClickable={!!onTransmitterClick} diff --git a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/index.tsx b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/index.tsx index 211a6e93b3..8fde2bbea8 100644 --- a/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/index.tsx +++ b/src/components/QuranReader/ReadingView/StudyModeModal/tabs/StudyModeQiraatTab/index.tsx @@ -20,6 +20,8 @@ import { } from '@/redux/slices/QuranReader/studyMode'; import { openReaderBioModal } from '@/redux/slices/QuranReader/verseActionModal'; +const DEFAULT_COLOR = '#FFFFFF'; + interface StudyModeQiraatTabProps { chapterId: string; verseNumber: string; @@ -71,6 +73,52 @@ const StudyModeQiraatTab: React.FC = ({ return data.junctures.find((juncture) => juncture.id === selectedJunctureId) ?? null; }, [data?.junctures, selectedJunctureId]); + // Compute reader-to-color and reader-to-reading maps for disambiguation. + // When a reader appears in multiple readings, we assign the reader to the + // first candidate reading whose color hasn't been used by another reader. + // This ensures each reading gets a unique color in the Readers panel. + const { readerColorMap, readerReadingMap } = useMemo(() => { + const colorMap = new Map(); + const readingMap = new Map(); + const usedColors = new Set(); + + const readingsList = selectedJuncture?.readings ?? []; + + const readerCandidates = (data?.readers ?? []).map((reader) => ({ + reader, + candidates: readingsList.filter( + ({ matrix }) => matrix?.readers?.includes(reader.id), + ), + })); + + // First pass: assign unambiguous readers (single candidate) + for (const { reader, candidates } of readerCandidates) { + if (candidates.length === 1) { + const color = candidates[0].color || DEFAULT_COLOR; + colorMap.set(reader.id, color); + readingMap.set(reader.id, candidates[0].id); + usedColors.add(color); + } + } + + // Second pass: for ambiguous readers, prefer a candidate color not yet used + for (const { reader, candidates } of readerCandidates) { + if (candidates.length <= 1) continue; + + const unusedCandidate = candidates.find( + ({ color }) => color && !usedColors.has(color), + ); + + const chosen = unusedCandidate || candidates[0]; + const color = chosen.color || DEFAULT_COLOR; + colorMap.set(reader.id, color); + readingMap.set(reader.id, chosen.id); + usedColors.add(color); + } + + return { readerColorMap: colorMap, readerReadingMap: readingMap }; + }, [data?.readers, selectedJuncture?.readings]); + // Handlers const handleJunctureSelect = useCallback((junctureId: number) => { setSelectedJunctureId(junctureId); @@ -140,15 +188,15 @@ const StudyModeQiraatTab: React.FC = ({ const transmitter = data?.transmitters?.find((tr) => tr.id === transmitterId); if (!transmitter) return undefined; - // 3. Find and scroll to a reading where that reader appears in the matrix - const readerReading = selectedJuncture.readings.find((reading) => - reading.matrix?.readers?.includes(transmitter.readerId), - ); - - if (readerReading) scrollToReading(readerReading.id); + // 3. Use the same disambiguation result as the color assignment, so the + // scroll target matches the reader's color in the Readers panel. + const readingId = readerReadingMap.get(transmitter.readerId); + if (readingId) { + scrollToReading(readingId); + } return undefined; }, - [selectedJuncture?.readings, data?.transmitters], + [selectedJuncture?.readings, data?.transmitters, readerReadingMap], ); if (isLoading) { @@ -187,6 +235,7 @@ const StudyModeQiraatTab: React.FC = ({ readers={data.readers} transmitters={data.transmitters} readings={selectedJuncture?.readings || []} + readerColorMap={readerColorMap} isExpanded={isReadersPanelExpanded} onToggleExpand={handleToggleReadersPanel} onTransmitterClick={handleTransmitterClick}