OSA and promotion details page updated

This commit is contained in:
CPM
2025-08-14 09:25:09 +05:30
parent 55fd811683
commit 6900df4fdf
6 changed files with 872 additions and 51 deletions
+405
View File
@@ -0,0 +1,405 @@
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, ScrollView, Image, Modal, Platform, StyleSheet, Alert } from 'react-native';
import { useRoute, useNavigation } from '@react-navigation/native';
import { post } from '../../../api/ApiService';
import IMAGES from '../../../constants/Images';
import { GlobalTheme } from '../../../theme';
import Loader from '../../../constants/Loader';
import { SafeAreaView } from 'react-native-safe-area-context';
import { toastError } from '../../../constants/Toast';
const Details = () => {
const route = useRoute();
const navigation = useNavigation();
const { selectedDetails = [], storeData, year, month, mainTabIndex } = route.params || {};
const [modalGraphData, setModalGraphData] = useState({});
const [activePromoTab, setActivePromoTab] = useState('Executed');
const [allCatData, setAllCatData] = useState([])
const [selectedCategoryData, setSelectedCategoryData] = useState([]);
const [categoryModalVisible, setCategoryModalVisible] = useState(false);
useEffect(() => {
if (selectedDetails.length > 0) {
fetchDetailGraphs(selectedDetails);
}
}, [selectedDetails]);
useEffect(() => {
getAllCatData();
}, [])
const getAllCatData = () => {
let data = {
parameters: {
projectid: 41654,
year: year,
monthno: month,
storeid: storeData?.StoreId
},
}
const apiUrl = mainTabIndex === 0
? 'https://dax.parinaam.in/execute/dabur/detmtd/oos_sku_list_for_all_visits_mtd'
: 'https://dax.parinaam.in/execute/dabur/detlsv/oos_sku_list_on_lsv';
post(apiUrl, data)
.then(res => {
setAllCatData(res?.data);
})
.catch(err => {
console.log('Error =>', err);
});
}
const fetchDetailGraphs = async (detailPages) => {
try {
const resultMap = {};
for (let item of detailPages) {
const response = await post(item.GraphUrl, {
parameters: {
projectid: 41654,
year: year,
monthno: month,
storeid: storeData?.StoreId
},
});
resultMap[item.GraphUrl] = response?.data || [];
}
setModalGraphData(resultMap);
} catch (error) {
console.log("❌ Error fetching detail graphs:", error);
}
};
const showCategoryDetails = (data) => {
setCategoryModalVisible(!categoryModalVisible)
// Filter SKUs from allCatData where the category name matches
const filteredCategory = allCatData.filter(item =>
item.Product_CategoryCategory_Name === data.Product_CategoryCategory_Name
);
// Save for display
setSelectedCategoryData(filteredCategory);
}
const isOSATab = selectedDetails.some(item => item.GraphTitle === "OSA - Category");
return (
<SafeAreaView style={{ flex: 1, backgroundColor: '#113F8C' }}>
<View style={{ flex: 1, backgroundColor: '#fff' }}>
{/* Header */}
<View style={styled.header}>
<TouchableOpacity onPress={() => navigation.goBack()} style={{ padding: 5 }}>
<Image source={IMAGES.backIcon} style={{ height: 20, width: 20, tintColor: '#fff' }} />
</TouchableOpacity>
<Text style={{ color: '#fff', fontSize: 20, fontWeight: 'bold' }}>Details</Text>
<View style={{ width: 25 }} /> {/* spacer */}
</View>
<ScrollView style={{ padding: 20 }}>
{selectedDetails.map((detail, index) => {
const values = modalGraphData[detail.GraphUrl] || [];
if (!modalGraphData[detail.GraphUrl]) {
return (
<View key={index} style={{ flex: 1, marginBottom: 20 }}>
<Loader visible={true} />
</View>
);
}
switch (detail.GraphType) {
case 'Table':
// Check if it's the Promotion table
const isPromotionTable =
values.length > 0 &&
values[0].hasOwnProperty('Product_CategoryCategory_Name') &&
values[0].hasOwnProperty('Promotion_MasterPromotion_Definition');
if (isPromotionTable) {
// Filter data based on active tab
const filteredValues = values.filter(row =>
activePromoTab === 'Executed'
? row.Executed?.toLowerCase() === 'yes'
: row.Executed?.toLowerCase() === 'no'
);
// Group data by Product_CategoryCategory_Name
const groupedData = filteredValues.reduce((acc, curr) => {
const category = curr.Product_CategoryCategory_Name || 'Unknown';
if (!acc[category]) acc[category] = [];
acc[category].push(curr);
return acc;
}, {});
return (
<View key={index} style={{ marginBottom: 10 }}>
{/* Horizontal Tabs - show once at top */}
{index === 0 && ( // ensures only first promotion table renders the toggle
<View style={{ flexDirection: 'row', marginBottom: 20 }}>
{['Executed', 'Not Executed'].map(tab => {
const isSelected = activePromoTab === tab;
return (
<TouchableOpacity
key={tab}
onPress={() => setActivePromoTab(tab)}
style={{
flex: 1,
paddingVertical: 10,
backgroundColor: isSelected ? '#113F8C' : '#E3EBF8',
borderRadius: 8,
marginHorizontal: 5,
alignItems: 'center',
}}
>
<Text style={{ color: isSelected ? '#fff' : '#000', fontWeight: '600' }}>
{tab}
</Text>
</TouchableOpacity>
);
})}
</View>
)}
{/* No data message */}
<View style={{}}>
{filteredValues.length === 0 ? (
<View style={{ marginTop: 0, backgroundColor: 'red' }} />
) : (
Object.keys(groupedData).map((categoryName, catIdx) => (
<View key={catIdx} style={{ marginBottom: 20 }}>
{/* Category Header */}
<View style={{ backgroundColor: '#E8F0FF', padding: 8, borderRadius: 6 }}>
<Text style={{ fontSize: 15, fontWeight: '700', color: '#113F8C' }}>
{String(categoryName)}
</Text>
</View>
{/* Table Header */}
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: '#f5f5f5',
paddingVertical: 8,
paddingHorizontal: 9,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
marginTop: 5
}}
>
<Text style={{ color: '#000', fontWeight: 'bold', width: '80%' }}>
Definition
</Text>
<Text style={{ color: '#000', fontWeight: 'bold', width: '20%', textAlign: 'center' }}>
Executed
</Text>
</View>
{/* Table Rows */}
<View
style={{
backgroundColor: '#fff',
paddingHorizontal: 10,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2
}}
>
{groupedData[categoryName].map((row, rowIdx) => (
<View
key={rowIdx}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 8,
borderBottomWidth: rowIdx === groupedData[categoryName].length - 1 ? 0 : 1,
borderColor: '#eee'
}}
>
<Text style={{ color: '#000', width: '80%' }}>
{String(row.Promotion_MasterPromotion_Definition) || '-'}
</Text>
<Text style={{ color: '#000', width: '20%', textAlign: 'center' }}>
{row.Executed || '-'}
</Text>
</View>
))}
</View>
</View>
))
)}
</View>
</View>
);
}
let displayKey = null;
let presentKey = null;
if (values.length > 0) {
const presentKeys = Object.keys(values[0]).filter(k =>
(typeof values[0][k] === 'string' && ['Yes', 'No'].includes(values[0][k])) ||
typeof values[0][k] === 'boolean' ||
typeof values[0][k] === 'number'
);
presentKey = presentKeys.length > 0 ? presentKeys[0] : null;
const displayKeys = Object.keys(values[0]).filter(k => k !== presentKey && typeof values[0][k] === 'string');
displayKey = displayKeys.length > 0 ? displayKeys[0] : null;
}
return (
<View key={index} style={{ marginBottom: 30 }}>
<Text style={{ color: '#000', fontSize: 16, fontWeight: '600', marginBottom: 10 }}>
{detail.GraphTitle}
</Text>
<View style={styled.categoryHeader}>
<Text style={{ color: '#000', fontWeight: 'bold', width: '80%' }}>
{displayKey ? displayKey.replace(/_/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2') : ''}
</Text>
<Text style={{ color: '#000', fontWeight: 'bold', width: '20%', textAlign: 'center' }}>
{presentKey ? presentKey.replace(/_/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2') : ''}
</Text>
</View>
<View style={styled.itemContainer}>
{values.map((row, idx) => {
const presentKeys = Object.keys(row).filter(k =>
(typeof row[k] === 'string' && ['Yes', 'No'].includes(row[k])) ||
typeof row[k] === 'boolean' ||
typeof row[k] === 'number'
);
const presentKey = presentKeys.length > 0 ? presentKeys[0] : null;
const displayKeys = Object.keys(row).filter(k => k !== presentKey && typeof row[k] === 'string');
const displayKey = displayKeys.length > 0 ? displayKeys[0] : null;
return (
<View
key={idx}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 8,
borderBottomWidth: idx === values.length - 1 ? 0 : 1,
borderColor: '#eee',
}}>
<Text style={{ color: '#000', width: '80%' }}>{row[displayKey] || '--'}</Text>
<TouchableOpacity disabled={isOSATab ? false : true}
onPress={() => {
if (row[presentKey] == 100 || row[presentKey] == '100') {
toastError('Alert', 'No Data Available')
} else {
showCategoryDetails(row)
}
}}
style={{ width: '20%', }}>
<Text style={{ color: '#113F8C', textAlign: 'center', fontWeight: '500' }}>
{presentKey ? (
typeof row[presentKey] === 'string' && ['Yes', 'No'].includes(row[presentKey]) ? row[presentKey] :
typeof row[presentKey] === 'boolean' ? (row[presentKey] ? 'Yes' : 'No') :
typeof row[presentKey] === 'number' ? row[presentKey].toFixed(2) : row[presentKey] || '-'
) : '-'}
</Text>
</TouchableOpacity>
</View>
);
})}
</View>
</View>
);
default:
return (
<View key={index} style={{ marginBottom: 20 }}>
<Text>Unsupported GraphType: {detail.GraphType}</Text>
</View>
);
}
})}
</ScrollView>
<Modal
visible={categoryModalVisible}
animationType="slide"
onRequestClose={() => setCategoryModalVisible(false)}
>
<View style={{ flex: 1, }}>
{/* Header */}
<View style={{ height: Platform.OS === 'ios' ? 55 : 0, backgroundColor: '#113F8C' }} />
<View style={{ width: '100%', backgroundColor: '#113F8C', borderBottomWidth: 1, borderColor: 'gray', padding: 5, paddingHorizontal: 20, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
<View style={{ width: '93%', alignItems: 'center' }}>
<Text style={{ color: '#fff', fontSize: 20, fontWeight: 'bold', marginBottom: 10, alignSelf: 'center' }}>{selectedCategoryData[0]?.Product_CategoryCategory_Name || 'Category'}</Text>
</View>
<TouchableOpacity onPress={() => setCategoryModalVisible(false)} style={{ width: '7%', alignItems: 'center' }}>
<Image source={IMAGES.crossIcon} style={{ height: 25, width: 25, resizeMode: 'contain' }} />
</TouchableOpacity>
</View>
{/* SKU List */}
<ScrollView contentContainerStyle={{ padding: 15 }}>
{/* <Text style={{ color: '#000', fontSize: 16, fontWeight: 'bold', marginBottom: 5 }}>OOS SKU details</Text> */}
{selectedCategoryData.map((item, idx) => (
<View
key={idx}
style={{
marginTop: 2,
flexDirection: 'row',
justifyContent: 'space-between',
paddingVertical: 8,
borderBottomWidth: idx === selectedCategoryData.length - 1 ? 0 : 1,
borderColor: '#eee',
}}
>
<Text style={{ color: '#000', width: '80%' }}>
{item.Product_MasterProduct_Name}
</Text>
{/* <Text style={{ color: '#000', width: '20%', textAlign: 'center' }}>
{item['#_of_OOS_SKU_for_all_visits']}
</Text> */}
</View>
))}
</ScrollView>
</View>
</Modal>
</View>
</SafeAreaView>
);
};
const styled = StyleSheet.create({
header: {
width: '100%',
backgroundColor: '#113F8C',
padding: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
categoryHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
backgroundColor: '#EDEDED',
paddingVertical: 8,
paddingHorizontal: 10,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
},
itemContainer: {
backgroundColor: '#fff',
paddingHorizontal: 10,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 2
}
})
export default Details;