diff --git a/backend/src/controller/Cell.ts b/backend/src/controller/Cell.ts index 79c37c7..994a546 100644 --- a/backend/src/controller/Cell.ts +++ b/backend/src/controller/Cell.ts @@ -62,7 +62,7 @@ export const getCellValuesbyYearandCtype = async ( while (hasMoreData) { const { data, error } = await supabase - .rpc("categorize_cells_by_year", { + .rpc("categorize_cells_by_year_flat", { selected_year: selected_year, category_type: classificationType, gte: gte, diff --git a/backend/src/controller/County.ts b/backend/src/controller/County.ts index f096e3b..0514386 100644 --- a/backend/src/controller/County.ts +++ b/backend/src/controller/County.ts @@ -11,9 +11,8 @@ export const getCounties = async ( if (supabase) { try { const { data, error } = await supabase - .from("Counties") - .select("county_id,province_id,county_data"); - // error handling in case the collection doesn't work + .from("County_Data") + .select("county_id,province_id,sid,soum_name,province_name"); if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -37,9 +36,8 @@ export const getCountyGeometry = async ( if (supabase) { try { const { data, error } = await supabase - .from("Counties") + .from("County_Data") .select("county_id,province_id,county_geometry"); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -64,10 +62,9 @@ export const getCountyByID = async ( try { const { county_id } = req.params; const { data, error } = await supabase - .from("Counties") - .select("county_id,province_id,county_data") + .from("County_Data") + .select("county_id,province_id,sid,soum_name,province_name") .eq("county_id", county_id); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -92,10 +89,9 @@ export const getCountyGeometryByID = async ( try { const { county_id } = req.params; const { data, error } = await supabase - .from("Counties") + .from("County_Data") .select("county_id,province_id,county_geometry") .eq("county_id", county_id); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -120,11 +116,10 @@ export const getCountyLivestockByID = async ( try { const { county_id, year } = req.params; const { data, error } = await supabase - .from("county_livestock") - .select("yearly_agg") + .from("counties_livestock") + .select("goat,camel,horse,sheep,cattle,total") .eq("asid", county_id) .eq("year", year); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -150,19 +145,18 @@ export const getCountyCellSummary = async ( const county_id = parseInt(req.params.county_id as string, 10); const category_type = req.params.category_type as string; - // Validate category_type if (!["carrying_capacity", "z_score"].includes(category_type)) { res.status(400).json({ log: "Invalid category type. Use 'carrying_capacity' or 'z_score'.", }); return; } - // Call the stored procedure using Supabase RPC - const { data, error } = await supabase.rpc("categorize_cells_by_county", { + + const { data, error } = await supabase.rpc("categorize_cells_by_new_county", { c_id: county_id, category_type: category_type, }); - // Error handling in case the query fails + if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -181,6 +175,7 @@ export const getCountyCellSummary = async ( } } }; + export const getCountiesGeomInProvince = async ( req: Request, res: Response @@ -189,12 +184,11 @@ export const getCountiesGeomInProvince = async ( try { const province_id = parseInt(req.params.province_id as string, 10); const { data, error } = await supabase.rpc( - "retrieve_counties_geom_in_province", + "retrieve_new_counties_geom_in_province", { p_id: province_id, } ); - // Error handling in case the query fails if (error) { res.status(500).json({ log: "Error while collecting the data", diff --git a/backend/src/controller/Province.ts b/backend/src/controller/Province.ts index 5c457b8..961a7ca 100644 --- a/backend/src/controller/Province.ts +++ b/backend/src/controller/Province.ts @@ -9,9 +9,8 @@ export const getProvinces = async ( if (supabase) { try { const { data, error } = await supabase - .from("Provinces") - .select("province_id,province_data"); - // error handling in case the collection doesn't work + .from("Province_Data") + .select("province_id,province_name,province_herders,province_land_area"); if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -19,9 +18,7 @@ export const getProvinces = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -35,9 +32,8 @@ export const getProvinceGeometry = async ( if (supabase) { try { const { data, error } = await supabase - .from("Provinces") - .select("province_id, province_data->province_name, province_geometry"); - // error handling in case the insertion doesn't work + .from("Province_Data") + .select("province_id,province_name,province_geometry"); if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -45,9 +41,7 @@ export const getProvinceGeometry = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -62,10 +56,9 @@ export const getProvinceByID = async ( try { const { province_id } = req.params; const { data, error } = await supabase - .from("Provinces") - .select("province_id,province_data") + .from("Province_Data") + .select("province_id,province_name,province_herders,province_land_area") .eq("province_id", province_id); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -73,9 +66,7 @@ export const getProvinceByID = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -90,10 +81,9 @@ export const getProvinceGeometryByID = async ( try { const { province_id } = req.params; const { data, error } = await supabase - .from("Provinces") + .from("Province_Data") .select("province_id,province_geometry") .eq("province_id", province_id); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -101,9 +91,7 @@ export const getProvinceGeometryByID = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -118,11 +106,10 @@ export const getProvinceLivestockByID = async ( try { const { province_id, year } = req.params; const { data, error } = await supabase - .from("province_livestock") - .select("yearly_agg") + .from("provinces_livestock") + .select("goat,camel,horse,sheep,cattle,total") .eq("asid_prefix", province_id) .eq("year", year); - // error handling in case the insertion doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -130,9 +117,7 @@ export const getProvinceLivestockByID = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -146,10 +131,9 @@ export const getProvinceLivestockByClass = async ( if (supabase) { try { const { type } = req.params; - const { data, error } = await supabase.rpc("fetch_livestock_by_class", { + const { data, error } = await supabase.rpc("fetch_new_livestock_by_class", { livestock_type: type, }); - // error handling in case the collection doesn't work if (error) { res.status(500).json({ log: "Error while collecting the data", @@ -157,9 +141,7 @@ export const getProvinceLivestockByClass = async ( }); return; } - res - .status(201) - .json({ log: "Data was successfully Collected", data: data }); + res.status(201).json({ log: "Data was successfully Collected", data: data }); } catch (error: any) { res.status(400).json({ message: error.message }); } @@ -175,7 +157,6 @@ export const getProvinceCellSummary = async ( const province_id = parseInt(req.params.province_id as string, 10); const category_type = req.params.category_type as string; - // Validate province_id if (province_id < 11 || province_id > 85) { res.status(400).json({ log: "Invalid province_id. It must be a valid number.", @@ -183,7 +164,6 @@ export const getProvinceCellSummary = async ( return; } - // Validate category_type if (!["carrying_capacity", "z_score"].includes(category_type)) { res.status(400).json({ log: "Invalid category type. Use 'carrying_capacity' or 'z_score'.", @@ -191,9 +171,8 @@ export const getProvinceCellSummary = async ( return; } - // Fetch data const { data, error } = await supabase.rpc( - "categorize_cells_by_province", + "categorize_cells_by_new_province", { p_id: province_id, category_type: category_type, @@ -208,13 +187,9 @@ export const getProvinceCellSummary = async ( return; } - res - .status(200) - .json({ log: "Data was successfully collected", data: data }); + res.status(200).json({ log: "Data was successfully collected", data: data }); } catch (error: any) { - res - .status(500) - .json({ log: "Internal server error", error: error.message }); + res.status(500).json({ log: "Internal server error", error: error.message }); } } }; @@ -237,7 +212,6 @@ export const getProvinceGR = async ( const parsedProvinceId = parseInt(province_id, 10); console.log(`Parsed province_id: ${parsedProvinceId}`); - // Validate province_id if ( isNaN(parsedProvinceId) || parsedProvinceId < 11 || @@ -250,18 +224,15 @@ export const getProvinceGR = async ( return; } - // Log before calling the RPC console.log(`Calling Supabase RPC with p_id: ${parsedProvinceId}`); - // Call Supabase function const { data, error } = await supabase.rpc( - "find_grazing_range_percentage", + "find_new_grazing_range_percentage", { p_id: parsedProvinceId, } ); - // Log response from Supabase console.log("Supabase RPC Response:", { data, error }); if (error) { diff --git a/frontend/src/components/charts/Table.tsx b/frontend/src/components/charts/Table.tsx index e74dc8b..f46aa68 100644 --- a/frontend/src/components/charts/Table.tsx +++ b/frontend/src/components/charts/Table.tsx @@ -127,32 +127,47 @@ const Table: React.FC = ({ columns, rows, loading = false, page: pro - {loading ? ( - - - Loading... + {loading ? ( + + + Loading... + + + ) : ( + sortedRows + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row, index) => { + const absoluteIndex = page * rowsPerPage + index; + return ( + + {columns.map((column) => { + // Special handling for "ranking" column + if (column.field === 'ranking') { + return ( + + {absoluteIndex + 1} + + ); + } + + return ( + + {column.format ? column.format(row[column.field]) : row[column.field]} - - ) : ( - sortedRows - .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map((row) => ( - - {columns.map((column) => ( - - {column.format ? column.format(row[column.field]) : row[column.field]} - - ))} - - )) - )} - + ); + })} + + ); + }) + )} + + { const [livestockData, setLivestockData] = useState<{ [key: string]: LivestockData[] }>({}); - const [provinceSummaries, setProvinceSummaries] = useState([]); - const [countySummaries, setCountySummaries] = useState([]); + const [provinceSummaries, setProvinceSummaries] = useState([]); const [provinceIds, setProvinceIds] = useState([]); - const [countyIds, setCountyIds] = useState([]); const [loading, setLoading] = useState(true); const [tabValue, setTabValue] = useState(0); - const [page, setPage] = useState(0); - const [provinces, setProvinces] = useState([]); - const [counties, setCounties] = useState([]); - + const livestockTypes = ["cattle", "horse", "goat", "camel", "sheep"]; const dzudYears = [2017, 2016, 2020, 2021]; const privatizationPeriods = [2012, 2015, 2018, 2013]; const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); - setPage(0); }; const tableColumns = [ - { field: 'ranking', headerName: 'Ranking', width: 100 }, - { field: 'name', headerName: tabValue === 0 ? "Aimag" : "Soum", width: 200 }, - ...(tabValue === 1 ? [{ field: 'aimag', headerName: 'Aimag', width: 200 }] : []), + { field: 'ranking', headerName: 'Ranking', width: 100, format: (value: number) => value.toString() }, + { field: 'aimag', headerName: 'Aimag', width: 200 }, { field: 'belowCapacity', headerName: 'Below Capacity', width: 150, format: (value: number) => `${value}%` }, { field: 'atCapacity', headerName: 'At Capacity', width: 150, format: (value: number) => `${value}%` }, { field: 'aboveCapacity', headerName: 'Above Capacity', width: 150, format: (value: number) => `${value}%` }, @@ -50,154 +41,109 @@ const InsightsPanel: React.FC = () => { const fetchProvinceIds = async () => { try { + setLoading(true); const response = await fetch("http://localhost:8080/api/province"); const json = await response.json(); - if (json.data) { - setProvinceIds(json.data.map((province: any) => province.province_id)); - setProvinces(json.data); - } - } catch (error) { - console.error("Error fetching province IDs:", error); - } - }; - const fetchCountyIds = async () => { - try { - const response = await fetch("http://localhost:8080/api/county"); - const json = await response.json(); if (json.data) { - const ids = json.data.map((county: any) => county.county_id); - setCountyIds(json.data.map((county: any) => county.county_id)); - setCounties(json.data); + const ids = json.data.map((province: any) => province.province_id); + setProvinceIds(ids); } } catch (error) { - console.error("Error fetching county IDs:", error); + console.error("Error fetching province IDs:", error); } }; - const getProvinceNameById = (id: number): string => { - const province = provinces.find((p) => p.province_id === id); - return province?.province_data?.province_name ?? "Unknown Province"; - }; - - const getSoumNameById = (id: number): string => { - const county = counties.find((c) => c.county_id === id); - return county?.county_data?.soum_name ?? "Unknown Soum"; - }; - - const getProvinceNameFromCountyId = (id: number): string => { - const county = counties.find((c) => c.county_id === id); - return county?.county_data?.province_name ?? "Unknown Province"; - }; - - const fetchSummariesFromIds = async ( - ids: number[], - endpoint: "province" | "county", - setSummaries: React.Dispatch> - ) => { + const fetchProvinceSummaries = async () => { try { setLoading(true); - const promises = ids.map(async (id) => { - try { - const response = await fetch( - `http://localhost:8080/api/${endpoint}/${id}/carrying_capacity/cell-summary` + const yearRange = Array.from({ length: 2022 - 2011 + 1 }, (_, i) => 2011 + i); + + const summariesByYear = await Promise.all( + provinceIds.map(async (provinceId) => { + const yearPromises = yearRange.map(year => + fetch(`http://localhost:8080/api/province/${provinceId}/carrying_capacity/cell-summary?year=${year}`) + .then(response => response.json()) + .catch(error => { + console.error(`Error fetching data for province ${provinceId}, year ${year}:`, error); + return null; + }) ); - const text = await response.text(); - if (response.headers.get("content-type")?.includes("application/json")) { - const jsonData = JSON.parse(text); - return { id, data: jsonData.data }; - } else { - console.error(`Unexpected format for ${endpoint} ${id}:`, text); - return null; - } - } catch (error) { - console.error(`Error fetching summary for ${endpoint} ${id}:`, error); - return null; - } - }); + const yearResults = await Promise.all(yearPromises); + const validYearResults = yearResults.filter(result => result && result.data && result.data.length > 0); - const results = await Promise.all(promises); - const validResults = results.filter( - (result): result is { id: number; data: any } => - result !== null && result.data && result.data.length > 0 + if (validYearResults.length === 0) return null; + + // Calculate averages across years + const averages = validYearResults.reduce((acc, result) => { + acc.belowSum += result.data[0].cat1_percentage; + acc.atSum += result.data[0].cat2_percentage; + acc.aboveSum += result.data[0].cat3_percentage; + return acc; + }, { belowSum: 0, atSum: 0, aboveSum: 0 }); + + const count = validYearResults.length; + + return { + provinceName: validYearResults[0].data[0].province_name, + belowCapacity: Math.round(averages.belowSum / count * 100) / 100, + atCapacity: Math.round(averages.atSum / count * 100) / 100, + aboveCapacity: Math.round(averages.aboveSum / count * 100) / 100 + }; + }) ); - const formattedSummaries = validResults.map((result, index) => { - const record = result.data[0]; - const id = result.id; - - const name = - endpoint === "province" - ? getProvinceNameById(id) - : getSoumNameById(id); - - const aimag = - endpoint === "province" - ? "" - : getProvinceNameFromCountyId(id); - - return { - ranking: index + 1, - name, - aimag, - belowCapacity: record?.cat1_percentage ?? 0, - atCapacity: record?.cat2_percentage ?? 0, - aboveCapacity: record?.cat3_percentage ?? 0, - }; - }); + const validSummaries = summariesByYear.filter(summary => summary !== null); - formattedSummaries.sort((a, b) => { - if (b.aboveCapacity !== a.aboveCapacity) { - return b.aboveCapacity - a.aboveCapacity; // Descending - } else if (b.atCapacity !== a.atCapacity) { - return b.atCapacity - a.atCapacity; // Descending - } else { - return a.belowCapacity - b.belowCapacity; // Ascending - } + const formattedSummaries = validSummaries.map((summary, index) => ({ + ranking: index + 1, + aimag: summary.provinceName, + belowCapacity: summary.belowCapacity, + atCapacity: summary.atCapacity, + aboveCapacity: summary.aboveCapacity, + })); + + // Sort by belowCapacity percentage + formattedSummaries.sort((a, b) => a.belowCapacity - b.belowCapacity); + + // Update rankings after sorting + formattedSummaries.forEach((summary, index) => { + summary.ranking = index + 1; }); - formattedSummaries.forEach((summary, index) => (summary.ranking = index + 1)); - setSummaries(formattedSummaries); + setProvinceSummaries(formattedSummaries); + setLoading(false); } catch (error) { - console.error("Error fetching summaries:", error); - } finally { + console.error("Error fetching province summaries:", error); setLoading(false); } - }; useEffect(() => { fetchProvinceIds(); - fetchCountyIds(); }, []); useEffect(() => { if (provinceIds.length > 0) { - fetchSummariesFromIds(provinceIds, "province", setProvinceSummaries); + fetchProvinceSummaries(); } }, [provinceIds]); - - useEffect(() => { - if (countyIds.length > 0) { - fetchSummariesFromIds(countyIds, "county", setCountySummaries); - } - }, [countyIds]); useEffect(() => { - const fetchLivestockData = async () => { + const fetchData = async () => { try { const allData: { [key: string]: LivestockData[] } = {}; await Promise.all( livestockTypes.map(async (type) => { const response = await fetch(`http://localhost:8080/api/provincebyclass/${type}`); - const json = await response.json(); + const json_object = await response.json(); const formattedData = [{ aimag: type, - data: json.data.map((item: any) => ({ + data: json_object.data.map((item: any) => ({ x: item.year, - y: item.livestock_count, - })), + y: item.livestock_count + })) }]; allData[type] = formattedData; }) @@ -208,29 +154,67 @@ const InsightsPanel: React.FC = () => { setLoading(false); } }; - - fetchLivestockData(); - }, []); + + fetchData(); + }, []); return ( - - - Key Conclusions + + + Key conclusions - - + + Regions by Carrying Capacity & Breakdown - - + @@ -238,16 +222,21 @@ const InsightsPanel: React.FC = () => { setPage(newPage)} -/> + columns={tableColumns} + rows={provinceSummaries} + loading={loading} + /> - - + + Livestock Population by Type and Year { minWidth: '1500px', width: '100%', height: '600px', - }, + } }} > {Object.keys(livestockData).length > 0 && ( @@ -271,6 +260,19 @@ const InsightsPanel: React.FC = () => { privatizationPeriods={privatizationPeriods} /> )} + + Note: All livestock numbers are converted to sheep units using the following conversion rates: + Camel (5 units), Horse (7 units), Cattle (6 units), Sheep (1 unit), Goat (0.9 units) + @@ -279,4 +281,4 @@ const InsightsPanel: React.FC = () => { ); }; -export default InsightsPanel; +export default InsightsPanel; \ No newline at end of file