OSA and promotion details page updated
This commit is contained in:
@@ -263,7 +263,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_TEAM = JGDHGNH9XY;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = PerformicsStoreDNA/Info.plist;
|
||||
@@ -272,7 +272,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.5;
|
||||
MARKETING_VERSION = 1.6;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -292,7 +292,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 5;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
DEVELOPMENT_TEAM = JGDHGNH9XY;
|
||||
INFOPLIST_FILE = PerformicsStoreDNA/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
@@ -300,7 +300,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.5;
|
||||
MARKETING_VERSION = 1.6;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
|
||||
@@ -12,6 +12,7 @@ import Dashboard from '../screens/MainScreen/Dashboard';
|
||||
import FeedbackCategories from '../screens/MainScreen/Feedback/FeedbackCategories';
|
||||
import { Platform, StatusBar, View } from 'react-native';
|
||||
import Welcome from '../screens/MainScreen/WelcomePage';
|
||||
import Details from '../screens/MainScreen/Dashboard/Details';
|
||||
|
||||
const Stack = createNativeStackNavigator();
|
||||
|
||||
@@ -38,6 +39,8 @@ const Routes = () => {
|
||||
<Stack.Screen name="Dashboard" component={Dashboard} />
|
||||
<Stack.Screen name="Feedback" component={Feedback} />
|
||||
<Stack.Screen name="FeedbackCategories" component={FeedbackCategories} />
|
||||
<Stack.Screen name="Details" component={Details} />
|
||||
|
||||
</Stack.Navigator>
|
||||
<ToastComponent />
|
||||
</NavigationContainer>
|
||||
|
||||
@@ -0,0 +1,406 @@
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -303,7 +303,7 @@ const Dashboard = (props) => {
|
||||
[d2],
|
||||
(tx, results) => {
|
||||
|
||||
console.log("results===", results);
|
||||
// console.log("results===", results);
|
||||
const rows = [];
|
||||
for (let i = 0; i < results.rows.length; i++) {
|
||||
rows.push(results.rows.item(i));
|
||||
@@ -429,7 +429,7 @@ const Dashboard = (props) => {
|
||||
|
||||
const openBottomSheet = () => {
|
||||
refRBSheet.current.open()
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectStore = async (item) => {
|
||||
await insertStoreInfoDNALocal([item]);
|
||||
@@ -442,11 +442,11 @@ const Dashboard = (props) => {
|
||||
|
||||
getTabData(currentTab?.MainTabData);
|
||||
refRBSheet.current.close()
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectSubTab = (item) => {
|
||||
setActiveTab(item?.TabId)
|
||||
}
|
||||
};
|
||||
|
||||
const getFilterStateCity = async () => {
|
||||
try {
|
||||
@@ -516,7 +516,7 @@ const Dashboard = (props) => {
|
||||
setStoreList(resData)
|
||||
setLoading(false)
|
||||
|
||||
console.log('storeSearchApi====>', JSON.stringify(resData));
|
||||
// console.log('storeSearchApi====>', JSON.stringify(resData));
|
||||
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
@@ -707,6 +707,9 @@ const Dashboard = (props) => {
|
||||
);
|
||||
});
|
||||
|
||||
// console.log('mainTabIndex----->',mainTabIndex);
|
||||
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: GlobalTheme.colors.primary }}>
|
||||
<View style={{ flex: 1, backgroundColor: '#fff' }}>
|
||||
@@ -914,11 +917,23 @@ const Dashboard = (props) => {
|
||||
{firstItem && (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.8}
|
||||
// onPress={() => {
|
||||
// if (firstItem.clickable === 1 && firstItem.DetailsPage?.length > 0) {
|
||||
// setSelectedDetails(firstItem.DetailsPage);
|
||||
// setShowDetailsModal(true);
|
||||
// fetchDetailGraphs(firstItem.DetailsPage);
|
||||
// }
|
||||
// }}
|
||||
|
||||
onPress={() => {
|
||||
if (firstItem.clickable === 1 && firstItem.DetailsPage?.length > 0) {
|
||||
setSelectedDetails(firstItem.DetailsPage);
|
||||
setShowDetailsModal(true);
|
||||
fetchDetailGraphs(firstItem.DetailsPage);
|
||||
navigation.navigate('Details', {
|
||||
selectedDetails: firstItem.DetailsPage,
|
||||
storeData,
|
||||
year,
|
||||
month,
|
||||
mainTabIndex
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -17,12 +17,6 @@
|
||||
"TabRow": 1,
|
||||
"TabCol": 2
|
||||
},
|
||||
// {
|
||||
// "TabId": 3,
|
||||
// "TabName": "SOS Compliance",
|
||||
// "TabRow": 1,
|
||||
// "TabCol": 3
|
||||
// },
|
||||
{
|
||||
"TabId": 4,
|
||||
"TabName": "OSA",
|
||||
@@ -42,6 +36,9 @@
|
||||
"TabCol": 6
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
|
||||
"graphDetails": [
|
||||
{
|
||||
"TabId": 1,
|
||||
@@ -61,15 +58,6 @@
|
||||
"GraphBackground": "#E2C8FE",
|
||||
"GraphOptions": {}
|
||||
},
|
||||
// {
|
||||
// "TabId": 1,
|
||||
// "GraphId": 3,
|
||||
// "GraphType": "ScoreCard",
|
||||
// "GraphTitle": "SOS Compliance",
|
||||
// "GraphUrl": "https://dax.parinaam.in/execute/dabur/mtd/SOS_Compliance_Perc",
|
||||
// "GraphBackground": "#FFD7C3",
|
||||
// "GraphOptions": {}
|
||||
// },
|
||||
{
|
||||
"TabId": 1,
|
||||
"GraphId": 4,
|
||||
@@ -262,16 +250,26 @@
|
||||
"TabId": 6,
|
||||
"GraphId": 9,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Promotion Availability",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/promotion_availability_mtd",
|
||||
"GraphTitle": "Promotion Not Executed",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detmtd/promotion_not_executed_mtd",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
},
|
||||
{
|
||||
"TabId": 6,
|
||||
"GraphId": 10,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Promotion executed",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detmtd/promotion_executed_mtd",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"TabId": 6,
|
||||
"GraphId": 10,
|
||||
"GraphId": 11,
|
||||
"GraphType": "BarGraph",
|
||||
"GraphTitle": "Promotion",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpimtd/promotion_trend_perc_mtd",
|
||||
@@ -298,12 +296,6 @@
|
||||
"TabRow": 1,
|
||||
"TabCol": 2
|
||||
},
|
||||
// {
|
||||
// "TabId": 3,
|
||||
// "TabName": "SOS Compliance",
|
||||
// "TabRow": 1,
|
||||
// "TabCol": 3
|
||||
// },
|
||||
{
|
||||
"TabId": 4,
|
||||
"TabName": "OSA",
|
||||
@@ -342,15 +334,6 @@
|
||||
"GraphBackground": "#E2C8FE",
|
||||
"GraphOptions": {}
|
||||
},
|
||||
// {
|
||||
// "TabId": 1,
|
||||
// "GraphId": 3,
|
||||
// "GraphType": "ScoreCard",
|
||||
// "GraphTitle": "SOS Compliance",
|
||||
// "GraphUrl": "https://dax.parinaam.in/execute/dabur/lsv/sos_compliance_lsv_perc",
|
||||
// "GraphBackground": "#FFD7C3",
|
||||
// "GraphOptions": {}
|
||||
// },
|
||||
{
|
||||
"TabId": 1,
|
||||
"GraphId": 4,
|
||||
@@ -474,7 +457,7 @@
|
||||
"TabId": 5,
|
||||
"GraphId": 5,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Asset Availability",
|
||||
"GraphTitle": "Visibility",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/asset_availability_lsv",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
@@ -483,7 +466,7 @@
|
||||
"TabId": 5,
|
||||
"GraphId": 5,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Asset",
|
||||
"GraphTitle": "Additional Visibility",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/additional_visibility_lsv",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
@@ -524,7 +507,7 @@
|
||||
"TabId": 4,
|
||||
"GraphId": 7,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "SOS Actual - Category",
|
||||
"GraphTitle": "OSA - Category",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/osa_lsv_perc_on_category",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
@@ -554,8 +537,17 @@
|
||||
"TabId": 6,
|
||||
"GraphId": 9,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Promotion Availability",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/promotion_availability_lsv",
|
||||
"GraphTitle": "Promotion Not Executed",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/promotion_not_executed_lsv",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
},
|
||||
{
|
||||
"TabId": 6,
|
||||
"GraphId": 10,
|
||||
"GraphType": "Table",
|
||||
"GraphTitle": "Promotion executed",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/detlsv/promotion_executed_lsv",
|
||||
"GraphBackground": "#ECFFFA",
|
||||
"GraphOptions": {}
|
||||
}
|
||||
@@ -563,7 +555,7 @@
|
||||
},
|
||||
{
|
||||
"TabId": 6,
|
||||
"GraphId": 10,
|
||||
"GraphId": 11,
|
||||
"GraphType": "BarGraph",
|
||||
"GraphTitle": "Promotion",
|
||||
"GraphUrl": "https://dax.parinaam.in/execute/dabur/kpilsv/promotion_trend_lsv_perc",
|
||||
|
||||
Reference in New Issue
Block a user