@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react';
22import PropTypes from 'prop-types' ;
33import { useSelector } from 'react-redux' ;
44import moment from 'moment' ;
5+
56import {
67 Container ,
78 Button ,
@@ -65,8 +66,8 @@ const propertiesData = [
6566 { property : 'Room 5' , value : 3.0 } ,
6667] ;
6768
68- // const getClassNames = (baseClass, darkClass, darkMode) =>
69- // `${baseClass} ${darkMode ? darkClass : ''}`;
69+ const getClassNames = ( baseClass , darkClass , darkMode ) =>
70+ `${ baseClass } ${ darkMode ? darkClass : '' } ` ;
7071
7172function GraphCard ( { title, metricLabel, darkMode } ) {
7273 return (
@@ -156,12 +157,6 @@ CategoryControls.propTypes = {
156157 onToggleDD : PropTypes . func . isRequired ,
157158} ;
158159
159- // Helper function to get class names
160- const getClassNames = ( baseClass , darkClass , darkMode ) =>
161- `${ baseClass } ${ darkMode ? darkClass : '' } ` ;
162-
163- // Extracted header component
164-
165160const DashboardHeader = ( { darkMode, onBack } ) => (
166161 < header className = { styles . dashboardHeader } >
167162 < h1 className = { getClassNames ( styles . title , styles . darkText , darkMode ) } >
@@ -276,36 +271,37 @@ export function LBDashboard() {
276271 const [ selectedMetricKey , setSelectedMetricKey ] = useState ( DEFAULTS . DEMAND ) ;
277272 const [ openDD , setOpenDD ] = useState ( { DEMAND : false , REVENUE : false , VACANCY : false } ) ;
278273 const darkMode = useSelector ( state => state . theme . darkMode ) ;
279- // --- Villages backend data ---
280- const [ villagesRaw , setVillagesRaw ] = useState ( [ ] ) ;
281- const [ loadingVillages , setLoadingVillages ] = useState ( false ) ;
282- const [ villagesError , setVillagesError ] = useState ( null ) ;
283-
284- useEffect ( ( ) => {
285- let mounted = true ;
286-
287- ( async ( ) => {
288- try {
289- setLoadingVillages ( true ) ;
290- setVillagesError ( null ) ;
291-
292- const res = await httpService . get ( `${ ApiEndpoint } /villages` ) ;
293- if ( ! mounted ) return ;
294-
295- setVillagesRaw ( Array . isArray ( res ?. data ) ? res . data : [ ] ) ;
296- } catch ( e ) {
297- if ( ! mounted ) return ;
298- setVillagesError ( 'Failed to load villages' ) ;
299- setVillagesRaw ( [ ] ) ;
300- } finally {
301- if ( mounted ) setLoadingVillages ( false ) ;
302- }
303- } ) ( ) ;
304274
305- return ( ) => {
306- mounted = false ;
307- } ;
308- } , [ ] ) ;
275+ // --- Villages backend data ---
276+ const [ villagesRaw , setVillagesRaw ] = useState ( [ ] ) ;
277+ const [ loadingVillages , setLoadingVillages ] = useState ( false ) ;
278+ const [ villagesError , setVillagesError ] = useState ( null ) ;
279+
280+ useEffect ( ( ) => {
281+ let mounted = true ;
282+
283+ ( async ( ) => {
284+ try {
285+ setLoadingVillages ( true ) ;
286+ setVillagesError ( null ) ;
287+
288+ const res = await httpService . get ( `${ ApiEndpoint } /villages` ) ;
289+ if ( ! mounted ) return ;
290+
291+ setVillagesRaw ( Array . isArray ( res ?. data ) ? res . data : [ ] ) ;
292+ } catch ( e ) {
293+ if ( ! mounted ) return ;
294+ setVillagesError ( 'Failed to load villages' ) ;
295+ setVillagesRaw ( [ ] ) ;
296+ } finally {
297+ if ( mounted ) setLoadingVillages ( false ) ;
298+ }
299+ } ) ( ) ;
300+
301+ return ( ) => {
302+ mounted = false ;
303+ } ;
304+ } , [ ] ) ;
309305
310306 const dateRange = [
311307 moment ( )
@@ -318,70 +314,70 @@ useEffect(() => {
318314 const all = Object . values ( METRIC_OPTIONS ) . flat ( ) ;
319315 return ( all . find ( o => o . key === selectedMetricKey ) || { } ) . label || '' ;
320316 } ;
321- // Decide which numeric value to calculate for the bar chart
322- const effectiveMetric = useMemo ( ( ) => {
323- switch ( selectedMetricKey ) {
324- case 'avgBid' :
325- case 'finalPrice' :
326- return 'avgCurrentBid' ;
327- case 'pageVisits' :
328- case 'numBids' :
329- case 'avgRating' :
330- case 'occupancyRate' :
331- case 'avgStay' :
332- return 'totalCurrentBid' ;
333- default :
334- return 'totalCurrentBid' ;
335- }
336- } , [ selectedMetricKey ] ) ;
337-
338- const valueFormatter = useMemo ( ( ) => {
339- if ( selectedMetricKey === 'avgRating' ) return v => Number ( v ) . toFixed ( 2 ) ;
340- if ( selectedMetricKey === 'occupancyRate' ) return v => `${ v } %` ;
341- if ( selectedMetricKey === 'avgStay' ) return v => `${ v } days` ;
342- if ( selectedMetricKey === 'avgBid' || selectedMetricKey === 'finalPrice' ) {
343- return v => `₹${ Number ( v ) . toLocaleString ( ) } ` ;
344- }
345- return v => Number ( v ) ;
346- } , [ selectedMetricKey ] ) ;
347-
348- // Derive villagesData from backend
349- const villagesData = useMemo ( ( ) => {
350- if ( ! villagesRaw . length ) return [ ] ;
351-
352- return villagesRaw
353- . map ( v => {
354- const props = Array . isArray ( v . properties ) ? v . properties : [ ] ;
355- const bids = props . map ( p => Number ( p ?. currentBid || 0 ) ) ;
356- const sum = bids . reduce ( ( a , b ) => a + b , 0 ) ;
357- const avg = bids . length ? sum / bids . length : 0 ;
358-
359- const value = effectiveMetric === 'avgCurrentBid' ? avg : sum ;
360-
361- return {
362- village : v . name || v . regionId || 'Unknown' ,
363- value,
364- } ;
365- } )
366- . sort ( ( a , b ) => b . value - a . value )
367- . slice ( 0 , 20 ) ;
368- } , [ villagesRaw , effectiveMetric ] ) ;
369-
370- const stripVillageWord = s => {
371- const str = String ( s || '' ) ;
372- const suffix = ' village' ;
373- return str . toLowerCase ( ) . endsWith ( suffix ) ? str . slice ( 0 , str . length - suffix . length ) : str ;
374- } ;
375317
376- const villagesDataClean = useMemo (
377- ( ) =>
378- villagesData . map ( d => ( {
379- ...d ,
380- village : stripVillageWord ( d . village ) ,
381- } ) ) ,
382- [ villagesData ] ,
383- ) ;
318+ // Decide which numeric value to calculate for the bar chart
319+ const effectiveMetric = useMemo ( ( ) => {
320+ switch ( selectedMetricKey ) {
321+ case 'avgBid' :
322+ case 'finalPrice' :
323+ return 'avgCurrentBid' ;
324+ case 'pageVisits' :
325+ case 'numBids' :
326+ case 'avgRating' :
327+ case 'occupancyRate' :
328+ case 'avgStay' :
329+ return 'totalCurrentBid' ;
330+ default :
331+ return 'totalCurrentBid' ;
332+ }
333+ } , [ selectedMetricKey ] ) ;
334+
335+ const valueFormatter = useMemo ( ( ) => {
336+ if ( selectedMetricKey === 'avgRating' ) return v => Number ( v ) . toFixed ( 2 ) ;
337+ if ( selectedMetricKey === 'occupancyRate' ) return v => `${ v } %` ;
338+ if ( selectedMetricKey === 'avgStay' ) return v => `${ v } days` ;
339+ if ( selectedMetricKey === 'avgBid' || selectedMetricKey === 'finalPrice' ) {
340+ return v => `₹${ Number ( v ) . toLocaleString ( ) } ` ;
341+ }
342+ return v => Number ( v ) ;
343+ } , [ selectedMetricKey ] ) ;
344+
345+ // Derive villagesData from backend
346+ const villagesData = useMemo ( ( ) => {
347+ if ( ! villagesRaw . length ) return [ ] ;
348+
349+ return villagesRaw
350+ . map ( v => {
351+ const props = Array . isArray ( v . properties ) ? v . properties : [ ] ;
352+ const bids = props . map ( p => Number ( p ?. currentBid || 0 ) ) ;
353+ const sum = bids . reduce ( ( a , b ) => a + b , 0 ) ;
354+ const avg = bids . length ? sum / bids . length : 0 ;
355+
356+ const value = effectiveMetric === 'avgCurrentBid' ? avg : sum ;
357+
358+ return {
359+ village : v . name || v . regionId || 'Unknown' ,
360+ value,
361+ } ;
362+ } )
363+ . sort ( ( a , b ) => b . value - a . value )
364+ . slice ( 0 , 20 ) ;
365+ } , [ villagesRaw , effectiveMetric ] ) ;
366+
367+ const stripVillageWord = s => {
368+ const str = String ( s || '' ) ;
369+ const suffix = ' village' ;
370+ return str . toLowerCase ( ) . endsWith ( suffix ) ? str . slice ( 0 , str . length - suffix . length ) : str ;
371+ } ;
384372
373+ const villagesDataClean = useMemo (
374+ ( ) =>
375+ villagesData . map ( d => ( {
376+ ...d ,
377+ village : stripVillageWord ( d . village ) ,
378+ } ) ) ,
379+ [ villagesData ] ,
380+ ) ;
385381
386382 const handleCategoryClick = category => {
387383 setActiveCategory ( category ) ;
@@ -418,19 +414,18 @@ const villagesDataClean = useMemo(
418414 />
419415
420416 < AnalysisSection title = "By Village" darkMode = { darkMode } >
421- < div className = { styles . chartRow } >
422- < div className = { styles . chartCol } >
423-
417+ < Row xs = "1" md = "3" className = "g-3" >
418+ < Col >
424419 < DemandOverTime
425420 compareType = "villages"
426421 metric = { mappedMetric }
427422 chartLabel = "Comparing Demand of Villages across Months"
428423 darkMode = { darkMode }
429424 dateRange = { dateRange }
430425 />
431- </ div >
426+ </ Col >
432427
433- < div className = { styles . chartCol } >
428+ < Col >
434429 { loadingVillages && (
435430 < div className = { getClassNames ( '' , styles . darkText , darkMode ) } > Loading villages…</ div >
436431 ) }
@@ -462,27 +457,27 @@ const villagesDataClean = useMemo(
462457 ] }
463458 />
464459 ) }
465- </ div >
460+ </ Col >
466461
467- < div className = { styles . chartCol } >
462+ < Col >
468463 < GraphCard title = "Comparing Villages" metricLabel = { metricLabel } darkMode = { darkMode } />
469- </ div >
470- </ div >
464+ </ Col >
465+ </ Row >
471466 </ AnalysisSection >
472467
473468 < AnalysisSection title = "By Property" darkMode = { darkMode } >
474- < div className = { styles . chartRow } >
475- < div className = { styles . chartCol } >
469+ < Row xs = "1" md = "2" className = "g-3" >
470+ < Col >
476471 < DemandOverTime
477472 compareType = "properties"
478473 metric = { mappedMetric }
479474 chartLabel = "Comparing Demand of Properties across Time"
480475 darkMode = { darkMode }
481476 dateRange = { dateRange }
482477 />
483- </ div >
478+ </ Col >
484479
485- < div className = { styles . chartCol } >
480+ < Col >
486481 < CompareBarGraph
487482 title = "Comparing Ratings of Properties"
488483 orientation = "vertical"
@@ -505,9 +500,8 @@ const villagesDataClean = useMemo(
505500 { label : 'Properties' , value : 'ALL' } ,
506501 ] }
507502 />
508- </ div >
509- </ div >
510-
503+ </ Col >
504+ </ Row >
511505 </ AnalysisSection >
512506
513507 < AnalysisSection title = "Insights from Reviews" darkMode = { darkMode } >
0 commit comments