etender live and purchased list
This commit is contained in:
Generated
+13939
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -32,7 +32,7 @@
|
|||||||
"react-native": "0.79.0",
|
"react-native": "0.79.0",
|
||||||
"react-native-alert-notification": "^0.4.2",
|
"react-native-alert-notification": "^0.4.2",
|
||||||
"react-native-app-intro-slider": "^4.0.4",
|
"react-native-app-intro-slider": "^4.0.4",
|
||||||
"react-native-bouncy-checkbox": "^4.1.2",
|
"react-native-bouncy-checkbox": "^4.1.3",
|
||||||
"react-native-date-picker": "^5.0.13",
|
"react-native-date-picker": "^5.0.13",
|
||||||
"react-native-gesture-handler": "^2.30.0",
|
"react-native-gesture-handler": "^2.30.0",
|
||||||
"react-native-gifted-charts": "^1.4.70",
|
"react-native-gifted-charts": "^1.4.70",
|
||||||
|
|||||||
+2
-1
@@ -13,6 +13,7 @@ export const apiCall = async <R, D = {}>(
|
|||||||
try {
|
try {
|
||||||
const token: any = store.getState().token.token;
|
const token: any = store.getState().token.token;
|
||||||
|
|
||||||
|
console.log("token : ", token)
|
||||||
const response = await axios({
|
const response = await axios({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
@@ -25,7 +26,7 @@ export const apiCall = async <R, D = {}>(
|
|||||||
'Content-Type': contentType || 'application/json',
|
'Content-Type': contentType || 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log("url : ", url)
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error?.response?.data?.error;
|
const errorMessage = error?.response?.data?.error;
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ const NavigationString = {
|
|||||||
PurchaseList: 'PurchaseList',
|
PurchaseList: 'PurchaseList',
|
||||||
Emd: 'Emd',
|
Emd: 'Emd',
|
||||||
StartBidding: 'StartBidding',
|
StartBidding: 'StartBidding',
|
||||||
PurchaseAuctionList:'PurchaseAuctionList'
|
PurchaseAuctionList:'PurchaseAuctionList',
|
||||||
|
// tender
|
||||||
|
PurchaseTenderList:'PurchaseTenderList',
|
||||||
|
ProceedToPurTender:'ProceedToPurTender',
|
||||||
}
|
}
|
||||||
export default NavigationString
|
export default NavigationString
|
||||||
@@ -36,6 +36,8 @@ export default function MainStack() {
|
|||||||
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
<Stack.Screen name={NavigationString.Emd} component={Screens.Emd} options={option} />
|
||||||
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
|
<Stack.Screen name={NavigationString.StartBidding} component={Screens.StartBidding} options={option} />
|
||||||
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
|
<Stack.Screen name={NavigationString.PurchaseAuctionList} component={Screens.PurchaseAuctionList} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.PurchaseTenderList} component={Screens.PurchaseTenderList} options={option} />
|
||||||
|
<Stack.Screen name={NavigationString.ProceedToPurTender} component={Screens.ProceedToPurTender} options={option} />
|
||||||
|
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
export interface TenderItem {
|
||||||
|
TenderNumber: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ DEFAULT = EMPTY ARRAY
|
||||||
|
const initialState: TenderItem[] = [];
|
||||||
|
|
||||||
|
export const selectPurchaseTenderSlice = createSlice({
|
||||||
|
name: 'selectPurchaseTender',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
togglePurchaseTender: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<TenderItem>
|
||||||
|
) => {
|
||||||
|
const index = state.findIndex(
|
||||||
|
item => item.TenderNumber === action.payload.TenderNumber
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
state.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
state.push(action.payload);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearPurchaseTender: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
togglePurchaseTender,
|
||||||
|
clearPurchaseTender,
|
||||||
|
} = selectPurchaseTenderSlice.actions;
|
||||||
|
|
||||||
|
export default selectPurchaseTenderSlice.reducer;
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
tender: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tenderSlice = createSlice({
|
||||||
|
name: 'tender',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setTender: (state, { payload }) => {
|
||||||
|
state.tender = payload;
|
||||||
|
},
|
||||||
|
removeTender: (state) => {
|
||||||
|
state.tender = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const { setTender , removeTender} = tenderSlice.actions;
|
||||||
|
export default tenderSlice.reducer;
|
||||||
@@ -5,11 +5,15 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
|
|||||||
import {tokenSlice} from './Slices/Token';
|
import {tokenSlice} from './Slices/Token';
|
||||||
import {auctionSlice} from './Slices/SelectedAuction';
|
import {auctionSlice} from './Slices/SelectedAuction';
|
||||||
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
|
import selectPurchaseAuctionReducer from './Slices/selectPurchaseAuction'
|
||||||
|
import selectPurchaseTenderReducer from './Slices/SelectedPurchaseTender';
|
||||||
|
import { tenderSlice } from './Slices/SelectedTender';
|
||||||
|
|
||||||
const rootReducer = combineReducers({
|
const rootReducer = combineReducers({
|
||||||
token: tokenSlice.reducer,
|
token: tokenSlice.reducer,
|
||||||
auction: auctionSlice.reducer,
|
auction: auctionSlice.reducer,
|
||||||
selectPurchaseAuction: selectPurchaseAuctionReducer,
|
selectPurchaseAuction: selectPurchaseAuctionReducer,
|
||||||
|
selectPurchaseTender: selectPurchaseTenderReducer,
|
||||||
|
tender: tenderSlice.reducer,
|
||||||
});
|
});
|
||||||
const persistConfig = {
|
const persistConfig = {
|
||||||
key: 'root',
|
key: 'root',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||||||
import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native';
|
import { View, Text, FlatList, TouchableOpacity, StyleSheet, TextInput, Alert } from 'react-native';
|
||||||
import { pick } from '@react-native-documents/picker';
|
import { pick } from '@react-native-documents/picker';
|
||||||
import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated';
|
import Animated, { FadeInDown, FadeOutUp, Layout } from 'react-native-reanimated';
|
||||||
import BouncyCheckbox from 'react-native-bouncy-checkbox';
|
// import BouncyCheckbox from 'react-native-bouncy-checkbox';
|
||||||
import { InsideHeader, Skelton } from '../../Component/Index';
|
import { InsideHeader, Skelton } from '../../Component/Index';
|
||||||
import colors from '../../Constant/Color';
|
import colors from '../../Constant/Color';
|
||||||
import EmdComplete from './Component/EmdComplete';
|
import EmdComplete from './Component/EmdComplete';
|
||||||
@@ -111,14 +111,14 @@ const Emd = () => {
|
|||||||
<Text style={styles.subText}>EMD ₹ {item.emdAmount}</Text>
|
<Text style={styles.subText}>EMD ₹ {item.emdAmount}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<BouncyCheckbox
|
{/* <BouncyCheckbox
|
||||||
size={20}
|
size={20}
|
||||||
fillColor={colors.primary}
|
fillColor={colors.primary}
|
||||||
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
|
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
|
||||||
innerIconStyle={{ borderRadius: 6 }}
|
innerIconStyle={{ borderRadius: 6 }}
|
||||||
isChecked={selected}
|
isChecked={selected}
|
||||||
onPress={(checked) => toggleSelect(item, checked)}
|
onPress={(checked) => toggleSelect(item, checked)}
|
||||||
/>
|
/> */}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Text style={styles.meta}>
|
<Text style={styles.meta}>
|
||||||
@@ -181,7 +181,7 @@ const Emd = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.bottom}>
|
<View style={styles.bottom}>
|
||||||
<BouncyCheckbox
|
{/* <BouncyCheckbox
|
||||||
size={18}
|
size={18}
|
||||||
fillColor={colors.primary}
|
fillColor={colors.primary}
|
||||||
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
|
iconStyle={{ borderRadius: 5, borderWidth: 1.5, borderColor: colors.primary }}
|
||||||
@@ -190,7 +190,7 @@ const Emd = () => {
|
|||||||
onPress={() => setAcceptedPolicy(p => !p)}
|
onPress={() => setAcceptedPolicy(p => !p)}
|
||||||
text="I agree to Terms & Privacy Policy"
|
text="I agree to Terms & Privacy Policy"
|
||||||
textStyle={styles.policyText}
|
textStyle={styles.policyText}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const Login = () => {
|
|||||||
mutate({ password: values.password, emailAdd: values.email }, {
|
mutate({ password: values.password, emailAdd: values.email }, {
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
|
console.log("login token : ", res)
|
||||||
Dispatch(setToken({ ...res.data, token: res.token }))
|
Dispatch(setToken({ ...res.data, token: res.token }))
|
||||||
Navigation.dispatch(
|
Navigation.dispatch(
|
||||||
CommonActions.reset({
|
CommonActions.reset({
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
import React, { useCallback, useState, useEffect } from 'react';
|
||||||
|
import { View, FlatList, ActivityIndicator, RefreshControl, Text } from 'react-native';
|
||||||
|
import Animated, { FadeInDown, FadeOutUp } from 'react-native-reanimated';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useFetchLiveAuction, useFetchLiveTender } from '../../Services/BBPS/Hooks';
|
||||||
|
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||||
|
import { Skelton } from '../../Component/Index';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { togglePurchaseTender } from '../../Redux/Slices/SelectedPurchaseTender';
|
||||||
|
import LiveTenderTile from './LiveTenderTile';
|
||||||
|
|
||||||
|
const LiveTender = () => {
|
||||||
|
const navigation: any = useNavigation();
|
||||||
|
const Dispatch = useDispatch()
|
||||||
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||||
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isRefetching, refetch }: any = useFetchLiveTender();
|
||||||
|
const [showRefreshBanner, setShowRefreshBanner] = useState(false);
|
||||||
|
|
||||||
|
console.log("live tender SelectedPurchaseList : ", SelectedPurchaseList)
|
||||||
|
useEffect(() => {
|
||||||
|
if (isRefetching) {
|
||||||
|
setShowRefreshBanner(true);
|
||||||
|
} else {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setShowRefreshBanner(false);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [isRefetching]);
|
||||||
|
|
||||||
|
const loadMore = useCallback(() => {
|
||||||
|
if (hasNextPage && !isFetchingNextPage) {
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
|
}, [hasNextPage, isFetchingNextPage]);
|
||||||
|
|
||||||
|
const renderFooter = useCallback(() => {
|
||||||
|
if (!isFetchingNextPage) return null;
|
||||||
|
return (
|
||||||
|
<View style={{ paddingVertical: 12 }}>
|
||||||
|
<ActivityIndicator size="small" color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}, [isFetchingNextPage]);
|
||||||
|
|
||||||
|
const renderItem = useCallback(
|
||||||
|
({ item, index }: any) => {
|
||||||
|
const isPurchase = SelectedPurchaseList.findIndex((prev: any) => prev.tenderId === item.tenderId)
|
||||||
|
return <LiveTenderTile
|
||||||
|
data={item}
|
||||||
|
index={index}
|
||||||
|
tenderId={item.tenderId}
|
||||||
|
tenderDispatchDate={item.purchaseStart}
|
||||||
|
isPurchased={isPurchase !== -1 ? true : false}
|
||||||
|
purchaseEnd={item.purchaseEnd}
|
||||||
|
tenderOfWork={item.departmentName}
|
||||||
|
tenderFees={item.documentFees}
|
||||||
|
tenderNumber={item.tenderNumber}
|
||||||
|
onPurchasePress={() => { Dispatch(togglePurchaseTender(item)) }}
|
||||||
|
onDetailsPress={() => { }}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
[SelectedPurchaseList]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Skelton />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<SearchInput onChange={() => { }} placeholder="Search...." />
|
||||||
|
|
||||||
|
{showRefreshBanner && (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInDown.duration(200)}
|
||||||
|
exiting={FadeOutUp.duration(500)}
|
||||||
|
style={{
|
||||||
|
alignSelf: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
marginTop: 6,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.06,
|
||||||
|
shadowRadius: 8,
|
||||||
|
shadowOffset: { width: 0, height: 3 },
|
||||||
|
elevation: 4,
|
||||||
|
zIndex: 10, // Ensure it stays on top
|
||||||
|
position: 'absolute',
|
||||||
|
top: 60, // Adjust based on your SearchInput height
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ActivityIndicator size="small" color={colors.primary} />
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#374151',
|
||||||
|
fontWeight: '500',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Refreshing auctions
|
||||||
|
</Text>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={data?.data ?? []}
|
||||||
|
keyExtractor={(item) => String(item.auctionId)}
|
||||||
|
renderItem={renderItem}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ paddingBottom: 50, paddingTop: 10 }}
|
||||||
|
onEndReached={loadMore}
|
||||||
|
onEndReachedThreshold={0.2}
|
||||||
|
ListFooterComponent={renderFooter}
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl
|
||||||
|
refreshing={isRefetching}
|
||||||
|
onRefresh={refetch}
|
||||||
|
tintColor="transparent" // Set to transparent if you only want your custom banner to show
|
||||||
|
colors={[colors.primary]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
initialNumToRender={6}
|
||||||
|
maxToRenderPerBatch={6}
|
||||||
|
windowSize={7}
|
||||||
|
removeClippedSubviews
|
||||||
|
updateCellsBatchingPeriod={50}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiveTender;
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
|
||||||
|
import moment from 'moment';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
import { LiveTenderTileProps } from './types/types';
|
||||||
|
import { Calendar, Tag, Info, ArrowRight } from 'lucide-react-native';
|
||||||
|
import TenderDetail from './Modal/TenderDetail';
|
||||||
|
|
||||||
|
const LiveTenderTile: React.FC<LiveTenderTileProps> = ({
|
||||||
|
data,
|
||||||
|
isPurchased = false,
|
||||||
|
onPurchasePress,
|
||||||
|
tenderDispatchDate,
|
||||||
|
tenderOfWork,
|
||||||
|
purchaseEnd,
|
||||||
|
tenderFees,
|
||||||
|
tenderNumber,
|
||||||
|
}) => {
|
||||||
|
const [showDetail, setShowDetail] = React.useState(false);
|
||||||
|
const purchaseEnded = moment().isAfter(moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.cardWrapper}>
|
||||||
|
{/* Status Tag */}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusTag,
|
||||||
|
{ backgroundColor: isPurchased ? colors.error : purchaseEnded ? '#94a3b8' : '#4ade80' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.container}
|
||||||
|
activeOpacity={0.9}
|
||||||
|
onPress={() => setShowDetail(true)}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<View style={styles.badge}>
|
||||||
|
<Tag size={14} color={colors.primary} />
|
||||||
|
<Text style={styles.badgeText}>#{tenderNumber}</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.priceText}>₹{tenderFees}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Work Title */}
|
||||||
|
<Text style={styles.workTitle} numberOfLines={2}>
|
||||||
|
{tenderOfWork ?? 'General Tender Work'}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* Info Grid */}
|
||||||
|
<View style={styles.infoGrid}>
|
||||||
|
<View style={styles.infoItem}>
|
||||||
|
<Calendar size={16} color="#64748b" />
|
||||||
|
<View style={styles.infoTextGroup}>
|
||||||
|
<Text style={styles.infoLabel}>Start</Text>
|
||||||
|
<Text style={styles.infoValue}>{tenderDispatchDate ?? '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.infoItem}>
|
||||||
|
<Calendar size={16} color={purchaseEnded ? "#ef4444" : "#22c55e"} />
|
||||||
|
<View style={styles.infoTextGroup}>
|
||||||
|
<Text style={styles.infoLabel}>Deadline</Text>
|
||||||
|
<Text style={[styles.infoValue, purchaseEnded && { color: '#ef4444' }]}>
|
||||||
|
{moment(purchaseEnd, 'DD-MM-YYYY HH:mm:ss').format('DD MMM, YY')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Action Bar */}
|
||||||
|
<View style={styles.actionBar}>
|
||||||
|
<TouchableOpacity style={styles.iconBtn} onPress={() => setShowDetail(true)}>
|
||||||
|
<Info size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.iconBtnText}>Details</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[
|
||||||
|
styles.mainButton,
|
||||||
|
purchaseEnded
|
||||||
|
? styles.btnDisabled
|
||||||
|
: isPurchased
|
||||||
|
? styles.btnPurchased
|
||||||
|
: styles.btnActive,
|
||||||
|
]}
|
||||||
|
onPress={onPurchasePress}
|
||||||
|
disabled={purchaseEnded}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.mainButtonText,
|
||||||
|
isPurchased && !purchaseEnded ? { color: colors.white } : { color: '#fff' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{purchaseEnded ? 'Expired' : isPurchased ? 'Remove' : 'Add to Cart'}
|
||||||
|
</Text>
|
||||||
|
{!purchaseEnded && (
|
||||||
|
<ArrowRight size={16} color={isPurchased ? colors.white : '#fff'} />
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Tender Detail Modal */}
|
||||||
|
<TenderDetail visible={showDetail} data={data} onClose={() => setShowDetail(false)} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(LiveTenderTile);
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
cardWrapper: {
|
||||||
|
marginVertical: 10,
|
||||||
|
marginHorizontal: 16,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 24,
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflow: 'hidden',
|
||||||
|
|
||||||
|
// Modern shadow
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 10 },
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 20,
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
statusTag: {
|
||||||
|
width: 6,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
badge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.primary + '25',
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 12,
|
||||||
|
gap: 6,
|
||||||
|
},
|
||||||
|
badgeText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: colors.primary,
|
||||||
|
},
|
||||||
|
priceText: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: '#0f172a',
|
||||||
|
},
|
||||||
|
workTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#334155',
|
||||||
|
lineHeight: 24,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
infoGrid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
backgroundColor: '#f0f4f8',
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
borderRadius: 16,
|
||||||
|
marginBottom: 18,
|
||||||
|
},
|
||||||
|
infoItem: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
infoTextGroup: {
|
||||||
|
flexDirection: 'column',
|
||||||
|
},
|
||||||
|
infoLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#94a3b8',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
infoValue: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#1e293b',
|
||||||
|
},
|
||||||
|
actionBar: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
},
|
||||||
|
iconBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 6,
|
||||||
|
borderRadius: 10,
|
||||||
|
backgroundColor: '#e2e8f0',
|
||||||
|
},
|
||||||
|
iconBtnText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: colors.primary,
|
||||||
|
},
|
||||||
|
mainButton: {
|
||||||
|
flex: 1,
|
||||||
|
height: 46,
|
||||||
|
borderRadius: 16,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
btnActive: {
|
||||||
|
backgroundColor: '#4ade80', // light green
|
||||||
|
},
|
||||||
|
btnPurchased: {
|
||||||
|
backgroundColor: colors.error,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: colors.error,
|
||||||
|
},
|
||||||
|
btnDisabled: {
|
||||||
|
backgroundColor: '#e2e8f0',
|
||||||
|
},
|
||||||
|
mainButtonText: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
Pressable,
|
||||||
|
} from 'react-native';
|
||||||
|
import DatePicker from 'react-native-date-picker';
|
||||||
|
import moment from 'moment';
|
||||||
|
import colors from '../../../Constant/Color';
|
||||||
|
import FontsFamily from '../../../Constant/FontsFamily';
|
||||||
|
|
||||||
|
type AuctionStatus = 'RUNNING' | 'ENDED' | 'NOT_STARTED' | null;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onApply: (filters: {
|
||||||
|
fromDate: Date | null;
|
||||||
|
toDate: Date | null;
|
||||||
|
status: AuctionStatus;
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilterModal: React.FC<Props> = ({ visible, onClose, onApply }) => {
|
||||||
|
const [fromDate, setFromDate] = useState<Date | null>(null);
|
||||||
|
const [toDate, setToDate] = useState<Date | null>(null);
|
||||||
|
const [status, setStatus] = useState<AuctionStatus>(null);
|
||||||
|
|
||||||
|
const [openPicker, setOpenPicker] = useState(false);
|
||||||
|
const [pickerType, setPickerType] = useState<'FROM' | 'TO'>('FROM');
|
||||||
|
|
||||||
|
const openDatePicker = (type: 'FROM' | 'TO') => {
|
||||||
|
setPickerType(type);
|
||||||
|
setOpenPicker(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirm = (date: Date) => {
|
||||||
|
if (pickerType === 'FROM') {
|
||||||
|
setFromDate(date);
|
||||||
|
} else {
|
||||||
|
setToDate(date);
|
||||||
|
}
|
||||||
|
setOpenPicker(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApply = () => {
|
||||||
|
onApply({ fromDate, toDate, status });
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
animationType="slide"
|
||||||
|
transparent
|
||||||
|
statusBarTranslucent
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
{/* Overlay */}
|
||||||
|
<Pressable style={styles.overlay} onPress={onClose} />
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.title}>Filter Auctions</Text>
|
||||||
|
|
||||||
|
{/* ===== DATE RANGE ===== */}
|
||||||
|
<Text style={styles.sectionTitle}>Date Range</Text>
|
||||||
|
|
||||||
|
<View style={styles.dateRow}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.dateBox}
|
||||||
|
onPress={() => openDatePicker('FROM')}
|
||||||
|
>
|
||||||
|
<Text style={styles.dateLabel}>From</Text>
|
||||||
|
<Text style={styles.dateValue}>
|
||||||
|
{fromDate
|
||||||
|
? moment(fromDate).format('DD MMM YYYY')
|
||||||
|
: 'Select date'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.dateBox}
|
||||||
|
onPress={() => openDatePicker('TO')}
|
||||||
|
>
|
||||||
|
<Text style={styles.dateLabel}>To</Text>
|
||||||
|
<Text style={styles.dateValue}>
|
||||||
|
{toDate
|
||||||
|
? moment(toDate).format('DD MMM YYYY')
|
||||||
|
: 'Select date'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ===== STATUS ===== */}
|
||||||
|
<Text style={styles.sectionTitle}>Auction Status</Text>
|
||||||
|
|
||||||
|
<View style={styles.statusRow}>
|
||||||
|
{[
|
||||||
|
{ label: 'Running', value: 'RUNNING' },
|
||||||
|
{ label: 'Ended', value: 'ENDED' },
|
||||||
|
{ label: 'Not Started', value: 'NOT_STARTED' },
|
||||||
|
].map(item => {
|
||||||
|
const active = status === item.value;
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={item.value}
|
||||||
|
onPress={() => setStatus(item.value as AuctionStatus)}
|
||||||
|
style={[
|
||||||
|
styles.statusPill,
|
||||||
|
active && styles.statusActive,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.statusText,
|
||||||
|
active && styles.statusActiveText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* ===== FOOTER ===== */}
|
||||||
|
<View style={styles.footer}>
|
||||||
|
{/* <TouchableOpacity style={styles.cancelBtn} onPress={onClose}>
|
||||||
|
<Text style={styles.cancelText}>Cancel</Text>
|
||||||
|
</TouchableOpacity> */}
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.applyBtn} onPress={handleApply}>
|
||||||
|
<Text style={styles.applyText}>Apply</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* ===== DATE PICKER ===== */}
|
||||||
|
<DatePicker
|
||||||
|
modal
|
||||||
|
open={openPicker}
|
||||||
|
date={
|
||||||
|
pickerType === 'FROM'
|
||||||
|
? fromDate || new Date()
|
||||||
|
: toDate || new Date()
|
||||||
|
}
|
||||||
|
mode="date"
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onCancel={() => setOpenPicker(false)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilterModal;
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.4)',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderTopLeftRadius: 22,
|
||||||
|
borderTopRightRadius: 22,
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '700',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
marginBottom: 10,
|
||||||
|
color: '#475569',
|
||||||
|
},
|
||||||
|
dateRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
dateBox: {
|
||||||
|
width: '48%',
|
||||||
|
padding: 14,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: '#F8FAFC',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E2E8F0',
|
||||||
|
},
|
||||||
|
dateLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: '#64748B',
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
dateValue: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#0F172A',
|
||||||
|
},
|
||||||
|
statusRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 10,
|
||||||
|
},
|
||||||
|
statusPill: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#CBD5E1',
|
||||||
|
backgroundColor: '#F8FAFC',
|
||||||
|
},
|
||||||
|
statusActive: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderColor: colors.primary,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#334155',
|
||||||
|
fontFamily:FontsFamily.poppinsSemiBold
|
||||||
|
},
|
||||||
|
statusActiveText: {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginTop: 24,
|
||||||
|
},
|
||||||
|
cancelBtn: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
|
||||||
|
applyBtn: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 12,
|
||||||
|
borderRadius: 12,
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
applyText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
textAlign: 'center',
|
||||||
|
fontFamily: FontsFamily.poppinsSemiBold
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
import React, { memo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
ScrollView,
|
||||||
|
SafeAreaView,
|
||||||
|
Platform,
|
||||||
|
StatusBar,
|
||||||
|
} from 'react-native';
|
||||||
|
import Modal from 'react-native-modal';
|
||||||
|
import moment from 'moment';
|
||||||
|
import {
|
||||||
|
Briefcase,
|
||||||
|
Calendar,
|
||||||
|
FileText,
|
||||||
|
Download,
|
||||||
|
Eye,
|
||||||
|
Layers,
|
||||||
|
ArrowLeft,
|
||||||
|
Clock,
|
||||||
|
} from 'lucide-react-native';
|
||||||
|
import colors from '../../../Constant/Color';
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
import { useFetchTenderById } from '../../../Services/BBPS/Hooks';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TenderFullDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||||
|
const inset = useSafeAreaInsets();
|
||||||
|
const { data: tenderResponse }: any = useFetchTenderById(data?.tenderId);
|
||||||
|
const tender = tenderResponse?.data?.[0];
|
||||||
|
|
||||||
|
const formatDate = (date?: string) => {
|
||||||
|
if (!date) return '-';
|
||||||
|
return moment(date, 'DD-MM-YYYY HH:mm:ss').format('DD MMM YYYY, hh:mm A');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!tender) return null;
|
||||||
|
|
||||||
|
const isLive = tender?.liveStatus === true;
|
||||||
|
|
||||||
|
const Section = ({ title, icon: Icon, children }: any) => (
|
||||||
|
<View style={styles.sectionContainer}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<Icon size={20} color={colors.primary} />
|
||||||
|
<Text style={styles.sectionTitle}>{title}</Text>
|
||||||
|
</View>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const InfoBox = ({ label, value, full = false }: any) => (
|
||||||
|
<View style={[styles.infoBox, full && { width: '100%' }]}>
|
||||||
|
<Text style={styles.label}>{label}</Text>
|
||||||
|
<Text style={styles.value}>{value || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isVisible={visible}
|
||||||
|
style={styles.fullModal}
|
||||||
|
animationIn="slideInRight"
|
||||||
|
animationOut="slideOutRight"
|
||||||
|
useNativeDriver
|
||||||
|
statusBarTranslucent
|
||||||
|
>
|
||||||
|
<SafeAreaView style={styles.safeArea}>
|
||||||
|
<StatusBar barStyle="dark-content" />
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||||
|
<ArrowLeft size={24} color="#1e293b" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={styles.headerTitle}>Tender Overview</Text>
|
||||||
|
<Text style={styles.headerSub}>Ref: {tender?.tenderNumber}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.liveBox}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.dot,
|
||||||
|
{ backgroundColor: isLive ? '#22c55e' : '#94a3b8' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Text style={styles.liveText}>{isLive ? 'LIVE' : 'CLOSED'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
|
|
||||||
|
{/* Basic Details */}
|
||||||
|
<Section title="Basic Details" icon={Layers}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox label="Tender No" value={tender?.tenderNumber} />
|
||||||
|
<InfoBox label="Type" value={tender?.typeOfTender} />
|
||||||
|
<InfoBox label="Category" value={tender?.tenderCategory} />
|
||||||
|
<InfoBox label="Mode" value={tender?.selectiveTender} />
|
||||||
|
<InfoBox label="Tender Status" value={tender?.tenderStatus} />
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Work & Estimate */}
|
||||||
|
<Section title="Work & Estimate" icon={Briefcase}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox label="Name of Work" value={tender?.nameOfWork} full />
|
||||||
|
<InfoBox label="Area of Work" value={tender?.areaOfWork} />
|
||||||
|
<InfoBox label="Estimate (Fig)" value={tender?.estimateValueInFig} />
|
||||||
|
<InfoBox label="Estimate (Words)" value={tender?.estimateValueInWord} />
|
||||||
|
<InfoBox
|
||||||
|
label="Completion Period"
|
||||||
|
value={`${tender?.workCompletionPeriod} ${tender?.workCompletionType}`}
|
||||||
|
/>
|
||||||
|
<InfoBox label="Rainy Season" value={tender?.rainySeason} />
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Department & Location */}
|
||||||
|
<Section title="Department & Location" icon={Layers}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox label="Department" value={tender?.deptName} full />
|
||||||
|
<InfoBox label="Circle" value={tender?.circle} />
|
||||||
|
<InfoBox label="Division" value={tender?.tenderSelectedDivision} />
|
||||||
|
<InfoBox label="Sub Division" value={tender?.tenderSelectedSubDivision} />
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Fee & Charges */}
|
||||||
|
<Section title="Fee & Charges" icon={FileText}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox label="Tender Fee" value={tender?.tenderFeeInFig} />
|
||||||
|
<InfoBox label="EMD" value={tender?.emdInFig} />
|
||||||
|
<InfoBox label="Portal Charge" value={tender?.portalCharge} />
|
||||||
|
<InfoBox label="GST (%)" value={tender?.gst} />
|
||||||
|
<InfoBox label="GST Amount" value={tender?.gstAmmount} />
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Bid Info */}
|
||||||
|
<Section title="Bid Information" icon={Eye}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox label="Envelope Type" value={tender?.envelopeType} />
|
||||||
|
<InfoBox label="Contractor Class" value={tender?.contractorClass} />
|
||||||
|
<InfoBox label="Bid Validation" value={`${tender?.bidValidatePeriod} ${tender?.bidValidateType}`} />
|
||||||
|
<InfoBox label="Re-Bid Allowed" value={tender?.tenderRebid} />
|
||||||
|
<InfoBox label="Bid Withdraw" value={tender?.tenderBidWithdraw} />
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Key Dates */}
|
||||||
|
<Section title="Key Dates" icon={Calendar}>
|
||||||
|
<View style={styles.grid}>
|
||||||
|
<InfoBox
|
||||||
|
label="Purchase Start"
|
||||||
|
value={formatDate(
|
||||||
|
tender?.tenderKeyDatesAmendment?.tenderPurchaseDate?.startDateTime
|
||||||
|
)}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
<InfoBox
|
||||||
|
label="Purchase End"
|
||||||
|
value={formatDate(
|
||||||
|
tender?.tenderKeyDatesAmendment?.tenderPurchaseDate?.endDateTime
|
||||||
|
)}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
<InfoBox
|
||||||
|
label="Bid Submission Start"
|
||||||
|
value={formatDate(
|
||||||
|
tender?.tenderKeyDatesAmendment?.bidSubmissionDate?.startDateTime
|
||||||
|
)}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
<InfoBox
|
||||||
|
label="Bid Submission End"
|
||||||
|
value={formatDate(
|
||||||
|
tender?.tenderKeyDatesAmendment?.bidSubmissionDate?.endDateTime
|
||||||
|
)}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Documents */}
|
||||||
|
<Section title="Documents" icon={Download}>
|
||||||
|
{tender?.tenderDocuments?.map((doc: any) => (
|
||||||
|
<View key={doc.docId} style={styles.fileRow}>
|
||||||
|
<FileText size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.fileName}>{doc.fileName}</Text>
|
||||||
|
<Download size={18} color="#94a3b8" />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
fullModal: {
|
||||||
|
margin: 0,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
safeArea: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 16,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#f1f5f9',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
backBtn: {
|
||||||
|
padding: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
headerTitleContainer: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
headerTitleText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: '#0f172a',
|
||||||
|
},
|
||||||
|
headerIdText: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
liveIndicator: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#f8fafc',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 20,
|
||||||
|
gap: 6,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e2e8f0',
|
||||||
|
},
|
||||||
|
dot: { width: 8, height: 8, borderRadius: 4 },
|
||||||
|
liveText: { fontSize: 11, fontWeight: '800', color: '#475569' },
|
||||||
|
|
||||||
|
scrollView: { flex: 1, backgroundColor: '#f8fafc' },
|
||||||
|
scrollContent: { padding: 16, paddingBottom: 100 },
|
||||||
|
|
||||||
|
sectionContainer: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#eef2f6',
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
marginBottom: 20,
|
||||||
|
paddingBottom: 10,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#f8fafc',
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: '#1e293b',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
infoBox: { marginBottom: 16 },
|
||||||
|
label: { fontSize: 12, color: '#94a3b8', fontWeight: '600', marginBottom: 4 },
|
||||||
|
value: { fontSize: 15, fontWeight: '700', color: '#334155' },
|
||||||
|
spacer: { height: 12 },
|
||||||
|
|
||||||
|
timelineContainer: { paddingLeft: 8 },
|
||||||
|
timelineRow: { flexDirection: 'row', alignItems: 'center', gap: 15 },
|
||||||
|
iconCircle: { width: 34, height: 34, borderRadius: 10, backgroundColor: '#f1f5f9', alignItems: 'center', justifyContent: 'center' },
|
||||||
|
timelineConnector: { width: 2, height: 20, backgroundColor: '#f1f5f9', marginLeft: 16 },
|
||||||
|
|
||||||
|
tableWrapper: { backgroundColor: '#f8fafc', borderRadius: 12, padding: 12 },
|
||||||
|
tableHead: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: '#e2e8f0', paddingBottom: 8, marginBottom: 12 },
|
||||||
|
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
||||||
|
tableRow: { flexDirection: 'row', alignItems: 'center' },
|
||||||
|
itemMain: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
||||||
|
itemSub: { fontSize: 12, color: '#64748b' },
|
||||||
|
itemQty: { flex: 1, fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||||
|
actionIcon: { padding: 6 },
|
||||||
|
|
||||||
|
fileCard: { flexDirection: 'row', alignItems: 'center', gap: 12, backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
||||||
|
fileIcon: { width: 40, height: 40, borderRadius: 8, backgroundColor: '#fee2e2', alignItems: 'center', justifyContent: 'center' },
|
||||||
|
fileName: { fontSize: 14, fontWeight: '700', color: '#334155' },
|
||||||
|
fileSize: { fontSize: 12, color: '#94a3b8' },
|
||||||
|
|
||||||
|
footer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: 20,
|
||||||
|
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#f1f5f9',
|
||||||
|
},
|
||||||
|
mainBtn: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
height: 56,
|
||||||
|
borderRadius: 16,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
shadowColor: colors.primary,
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.2,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 4,
|
||||||
|
},
|
||||||
|
btnDisabled: { backgroundColor: '#cbd5e1' },
|
||||||
|
mainBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' },
|
||||||
|
fileRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#f1f5f9',
|
||||||
|
width: '80%',
|
||||||
|
},
|
||||||
|
liveBox: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 20,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e2e8f0',
|
||||||
|
},
|
||||||
|
headerSub: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#64748b',
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: '#0f172a',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default memo(TenderFullDetail);
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
ScrollView,
|
||||||
|
SafeAreaView,
|
||||||
|
Platform,
|
||||||
|
} from 'react-native';
|
||||||
|
import Modal from 'react-native-modal';
|
||||||
|
import {
|
||||||
|
Package,
|
||||||
|
IndianRupee,
|
||||||
|
FileText,
|
||||||
|
Download,
|
||||||
|
ArrowLeft,
|
||||||
|
Info,
|
||||||
|
} from 'lucide-react-native';
|
||||||
|
import colors from '../../../Constant/Color';
|
||||||
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TenderItemDetail: React.FC<Props> = ({ visible, onClose, data }) => {
|
||||||
|
const inset = useSafeAreaInsets();
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Section = ({ title, icon: Icon, children }: any) => (
|
||||||
|
<View style={styles.sectionCard}>
|
||||||
|
<View style={styles.sectionHeader}>
|
||||||
|
<View style={styles.iconCircle}>
|
||||||
|
<Icon size={18} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.sectionTitle}>{title}</Text>
|
||||||
|
</View>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const PriceTile = ({ label, fig, words, full = false }: any) => (
|
||||||
|
<View style={[styles.priceTile, full && { width: '100%' }]}>
|
||||||
|
<Text style={styles.label}>{label}</Text>
|
||||||
|
<Text style={styles.priceFig}>
|
||||||
|
₹{Number(fig || 0).toLocaleString('en-IN')}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.priceWords}>{words || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isVisible={visible}
|
||||||
|
style={styles.fullModal}
|
||||||
|
animationIn="slideInUp"
|
||||||
|
animationOut="slideOutDown"
|
||||||
|
useNativeDriver
|
||||||
|
statusBarTranslucent
|
||||||
|
>
|
||||||
|
<SafeAreaView style={styles.safeArea}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={[styles.header, { paddingTop: inset.top }]}>
|
||||||
|
<TouchableOpacity onPress={onClose} style={styles.backBtn}>
|
||||||
|
<ArrowLeft size={24} color="#1e293b" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.headerTitle}>Item Information</Text>
|
||||||
|
<Text style={styles.headerSub}>Detail View</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
|
{/* Product Specification */}
|
||||||
|
<Section title="Product Specification" icon={Package}>
|
||||||
|
<View style={styles.identityHeader}>
|
||||||
|
<Text style={styles.productNameText}>
|
||||||
|
{data.productName || '-'}
|
||||||
|
</Text>
|
||||||
|
<View style={styles.categoryBadge}>
|
||||||
|
<Text style={styles.categoryText}>
|
||||||
|
{data.category || '-'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.specGrid}>
|
||||||
|
<View style={styles.specItem}>
|
||||||
|
<Text style={styles.label}>Sub Category</Text>
|
||||||
|
<Text style={styles.specValue}>{data.subCategory || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.specItem}>
|
||||||
|
<Text style={styles.label}>Duration</Text>
|
||||||
|
<Text style={styles.specValue}>{data.timeDuration || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Pricing */}
|
||||||
|
<Section title="Reserve & Bid Values" icon={IndianRupee}>
|
||||||
|
<PriceTile
|
||||||
|
label="Reserve Price"
|
||||||
|
fig={data.reservePriceFig}
|
||||||
|
words={data.reservePriceWords}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
<View style={styles.hDivider} />
|
||||||
|
<PriceTile
|
||||||
|
label="Tender Start Value"
|
||||||
|
fig={data.TenderStartFig}
|
||||||
|
words={data.TenderStartWords}
|
||||||
|
full
|
||||||
|
/>
|
||||||
|
<View style={styles.hDivider} />
|
||||||
|
<View style={styles.row}>
|
||||||
|
<PriceTile
|
||||||
|
label="Bid Variation"
|
||||||
|
fig={data.bidVariationFig}
|
||||||
|
words={data.bidVariationWords}
|
||||||
|
/>
|
||||||
|
<PriceTile
|
||||||
|
label="EMD Amount"
|
||||||
|
fig={data.emdFig}
|
||||||
|
words={data.emdWords}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
<Section title="Brief Description" icon={Info}>
|
||||||
|
<Text style={styles.descContent}>
|
||||||
|
{data.description || '-'}
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
{/* Documents */}
|
||||||
|
<Section title="Documents" icon={FileText}>
|
||||||
|
<TouchableOpacity style={styles.docRow}>
|
||||||
|
<Text style={styles.docIndex}>1</Text>
|
||||||
|
<View style={{ flex: 2 }}>
|
||||||
|
<Text style={styles.docTitle} numberOfLines={1}>
|
||||||
|
{data.documentName || 'Document'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.downloadIconBox}>
|
||||||
|
<Download size={18} color={colors.primary} />
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Section>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
fullModal: { margin: 0, backgroundColor: '#f8fafc' },
|
||||||
|
safeArea: { flex: 1 },
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: 16,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#f1f5f9',
|
||||||
|
},
|
||||||
|
backBtn: { padding: 8, marginRight: 8 },
|
||||||
|
headerTitle: { fontSize: 18, fontWeight: '800', color: '#0f172a' },
|
||||||
|
headerSub: { fontSize: 12, color: '#94a3b8', fontWeight: '600', textTransform: 'uppercase' },
|
||||||
|
|
||||||
|
scrollView: { flex: 1 },
|
||||||
|
scrollContent: { padding: 16, paddingBottom: 50 },
|
||||||
|
|
||||||
|
sectionCard: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 24,
|
||||||
|
padding: 20,
|
||||||
|
marginBottom: 16,
|
||||||
|
...Platform.select({
|
||||||
|
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.05, shadowRadius: 10 },
|
||||||
|
android: { elevation: 3 }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
sectionHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
iconCircle: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: colors.primary + '10',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: '#334155',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Product Identity
|
||||||
|
identityHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 15 },
|
||||||
|
productNameText: { fontSize: 20, fontWeight: '900', color: '#1e293b', flex: 1 },
|
||||||
|
categoryBadge: { backgroundColor: '#f1f5f9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20 },
|
||||||
|
categoryText: { fontSize: 11, fontWeight: '800', color: '#64748b', textTransform: 'uppercase' },
|
||||||
|
specGrid: { flexDirection: 'row', gap: 24, marginTop: 5 },
|
||||||
|
specItem: { flex: 1 },
|
||||||
|
specValue: { fontSize: 15, fontWeight: '700', color: '#334155', marginTop: 2 },
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
priceTile: { marginBottom: 16 },
|
||||||
|
label: { fontSize: 11, color: '#94a3b8', fontWeight: '700', textTransform: 'uppercase', marginBottom: 4 },
|
||||||
|
priceFig: { fontSize: 22, fontWeight: '900', color: colors.black },
|
||||||
|
priceWords: { fontSize: 13, color: '#64748b', fontStyle: 'italic', marginTop: 2 },
|
||||||
|
hDivider: { height: 1, backgroundColor: '#f1f5f9', marginBottom: 16 },
|
||||||
|
row: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||||
|
|
||||||
|
// Description
|
||||||
|
descContent: { fontSize: 15, color: '#475569', lineHeight: 22, fontWeight: '500' },
|
||||||
|
|
||||||
|
// Table
|
||||||
|
docTableHead: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#f1f5f9', marginBottom: 10 },
|
||||||
|
tableLabel: { fontSize: 11, fontWeight: '800', color: '#94a3b8', flex: 1 },
|
||||||
|
docRow: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#f8fafc', padding: 12, borderRadius: 12 },
|
||||||
|
docIndex: { flex: 1, fontSize: 14, fontWeight: '700', color: '#64748b' },
|
||||||
|
docTitle: { fontSize: 14, fontWeight: '700', color: '#1e293b' },
|
||||||
|
downloadIconBox: { backgroundColor: '#fff', padding: 8, borderRadius: 10, borderWidth: 1, borderColor: '#e2e8f0' },
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
footer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0, width: '100%',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
padding: 20,
|
||||||
|
paddingBottom: Platform.OS === 'ios' ? 34 : 20,
|
||||||
|
borderTopWidth: 1, borderTopColor: '#f1f5f9'
|
||||||
|
},
|
||||||
|
primaryBtn: {
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
height: 56, borderRadius: 18,
|
||||||
|
flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 10
|
||||||
|
},
|
||||||
|
primaryBtnText: { color: '#fff', fontSize: 16, fontWeight: '800' }
|
||||||
|
});
|
||||||
|
|
||||||
|
export default memo(TenderItemDetail);
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, FlatList, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
||||||
|
import { InsideHeader, Loader } from '../../Component/Index';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
import { CreditCard, Calendar, User, DollarSign, ArrowRight } from 'lucide-react-native';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useOfflineTenderPurchase } from '../../Services/BBPS/Hooks';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
|
||||||
|
const ProceedToPurTender = () => {
|
||||||
|
const navigation: any = useNavigation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||||
|
console.log(SelectedPurchaseList, 'iiiiiiiiiiiiiiiiiiiiiiii tender purchase list : ', SelectedPurchaseList);
|
||||||
|
|
||||||
|
const { mutate, isPending } = useOfflineTenderPurchase()
|
||||||
|
const calculateTotalAmount = (item: any) => {
|
||||||
|
const gstAmount =
|
||||||
|
(Number(item.documentFees) + Number(item.portalCharge)) *
|
||||||
|
(Number(item.gst) / 100);
|
||||||
|
|
||||||
|
const payableAmount =
|
||||||
|
Number(item.documentFees) + Number(item.portalCharge) + gstAmount;
|
||||||
|
return payableAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOfflinePurchase = async () => {
|
||||||
|
const orderNo = `ORD${Date.now()}`;
|
||||||
|
const body = {
|
||||||
|
tenderDetails: SelectedPurchaseList.map((item: any, index:number) => ({
|
||||||
|
gst: item.gst,
|
||||||
|
regId: item.regId || 0,
|
||||||
|
deptId: item.deptId || 1,
|
||||||
|
gstAmount: item.gst / 100,
|
||||||
|
payableAmount: calculateTotalAmount(item),
|
||||||
|
portalCharge: item.portalCharge,
|
||||||
|
tenderFees: item.documentFees,
|
||||||
|
tenderId: item.tenderId,
|
||||||
|
tenderNumber: item.tenderNumber,
|
||||||
|
tenderStatus: item.tenderStatus ?? "",
|
||||||
|
tenderType: item.tenderType ?? "",
|
||||||
|
isPurchased: item.purchaseStatus ?? false,
|
||||||
|
totalPayableAmount: calculateTotalAmount(item),
|
||||||
|
percentageAmount: calculateTotalAmount(item) / 100,
|
||||||
|
newTenderDocumentFees: item.documentFees * index + 1,
|
||||||
|
})),
|
||||||
|
orderNo: orderNo,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("tender offline body : ", body)
|
||||||
|
try {
|
||||||
|
// console.log(body);
|
||||||
|
mutate(body, {
|
||||||
|
onSuccess: (res) => {
|
||||||
|
console.log(res, '---------------------purchsase response');
|
||||||
|
Alert.alert('Offline Purchase Successful!', `Order No: ${orderNo}`);
|
||||||
|
navigation.goBack()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.log(error, "================purchse Error");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("purchase offline error", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePurchase = (type: 'online' | 'offline') => {
|
||||||
|
setLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
type === 'online' ? Alert.alert('Online Purchase Successful!') : handleOfflinePurchase();
|
||||||
|
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderItem = ({ item }: { item: any[0] }) => (
|
||||||
|
<View style={styles.card}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Text style={styles.title}>{item.auctionNumber}</Text>
|
||||||
|
<Text style={styles.pattern}>{item.auctionPattern}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRow}>
|
||||||
|
<Calendar size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.text}>End Date: {item.purchaseEnd}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRow}>
|
||||||
|
<User size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.text}>Tender No: {item.tenderNumber}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRow}>
|
||||||
|
<DollarSign size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.text}>Tender Fees: ₹{item.documentFees}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRow}>
|
||||||
|
<DollarSign size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.text}>GST ({item.gst}%): ₹{item.gst / 100}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.cardRow}>
|
||||||
|
<DollarSign size={18} color={colors.primary} />
|
||||||
|
<Text style={styles.text}>Portal Charge: ₹{item.portalCharge}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.totalRow}>
|
||||||
|
<CreditCard size={20} color={colors.white} />
|
||||||
|
<Text style={styles.total}>Total: ₹{calculateTotalAmount(item)}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, backgroundColor: colors.white }}>
|
||||||
|
<InsideHeader title="Tender Purchase" showArrow />
|
||||||
|
<View style={styles.screen}>
|
||||||
|
<FlatList
|
||||||
|
data={SelectedPurchaseList}
|
||||||
|
renderItem={renderItem}
|
||||||
|
keyExtractor={(item) => item.tenderId.toString()}
|
||||||
|
contentContainerStyle={styles.container}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.bottomButtonContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { marginRight: 8 }]}
|
||||||
|
onPress={() => handlePurchase('online')}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>Purchase Online</Text>
|
||||||
|
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, { marginLeft: 8 }]}
|
||||||
|
onPress={() => handlePurchase('offline')}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>Purchase Offline</Text>
|
||||||
|
<ArrowRight size={18} color="#fff" style={{ marginLeft: 6 }} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<View style={styles.loadingOverlay}>
|
||||||
|
<Loader />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProceedToPurTender;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
screen: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
padding: 16,
|
||||||
|
paddingBottom: 120,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
elevation: 5,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.12,
|
||||||
|
shadowOffset: { width: 0, height: 3 },
|
||||||
|
shadowRadius: 8,
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#0F172A',
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: colors.primary,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
cardRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 6,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#555',
|
||||||
|
},
|
||||||
|
totalRow: {
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
total: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
bottomButtonContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
width: '100%',
|
||||||
|
paddingVertical: 20,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
elevation: 5
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
paddingVertical: 14,
|
||||||
|
borderRadius: 12,
|
||||||
|
alignItems: 'center',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
loadingOverlay: {
|
||||||
|
...StyleSheet.absoluteFillObject,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.2)',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import React, { useState, useCallback, memo } from 'react';
|
||||||
|
import { View, Text, StyleSheet, FlatList, RefreshControl, ActivityIndicator } from 'react-native';
|
||||||
|
import LiveTenderTile from './LiveTenderTile';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { useFetchPurchaseTender } from '../../Services/BBPS/Hooks';
|
||||||
|
import PurchaseTenderTile from './PurchaseTenderTile';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { setTender } from '../../Redux/Slices/SelectedTender';
|
||||||
|
import { SearchInput } from '../../Component/Serach/SeacrhInput';
|
||||||
|
import { Skelton } from '../../Component/Index';
|
||||||
|
import { allBooleanProps } from './types/types';
|
||||||
|
import FilterModal from './Modal/FilterModal';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
|
||||||
|
|
||||||
|
const PurchaseTenderAllList = () => {
|
||||||
|
const navigation = useNavigation() as any;
|
||||||
|
const Dispatch = useDispatch()
|
||||||
|
|
||||||
|
const [allBoolean, setAllBoolean] = useState<allBooleanProps>({ isShowFilter: false })
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const { data: data, isPending, isFetching } = useFetchPurchaseTender(page);
|
||||||
|
|
||||||
|
console.log("data my data : ", data);
|
||||||
|
const [allData, setAllData] = useState<any[]>([]);
|
||||||
|
const format = 'DD-MM-YYYY HH:mm:ss';
|
||||||
|
|
||||||
|
const tenderList = data?.data || [];
|
||||||
|
const totalCount = data?.totalCount || 0;
|
||||||
|
|
||||||
|
// Append new page data
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (tenderList.length) {
|
||||||
|
setAllData(prev => {
|
||||||
|
const merged = [...prev, ...tenderList];
|
||||||
|
const unique = merged.filter(
|
||||||
|
(v, i, a) => a.findIndex(t => t.tenderId === v.tenderId) === i
|
||||||
|
);
|
||||||
|
return unique;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [tenderList]);
|
||||||
|
function isTenderActive(item: any): boolean {
|
||||||
|
const now = moment();
|
||||||
|
const start = moment(item?.keyDates?.TenderBidding?.startDateTime, format);
|
||||||
|
const end = moment(item?.keyDates?.TenderBidding?.endDateTime, format);
|
||||||
|
|
||||||
|
if (!start.isValid() || !end.isValid()) return false;
|
||||||
|
|
||||||
|
return now.isBetween(start, end, undefined, '[]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTenderEnd(item: any): boolean {
|
||||||
|
const now = moment();
|
||||||
|
const end = moment(item?.keyDates?.TenderBidding?.endDateTime, format);
|
||||||
|
|
||||||
|
if (!end.isValid()) return false;
|
||||||
|
|
||||||
|
return now.isAfter(end);
|
||||||
|
}
|
||||||
|
console.log(allBoolean);
|
||||||
|
|
||||||
|
const loadMore = () => {
|
||||||
|
if (allData.length < totalCount && !isFetching) {
|
||||||
|
setPage(prev => prev + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const renderFooter = () => {
|
||||||
|
if (!isFetching) return null;
|
||||||
|
return (
|
||||||
|
<ActivityIndicator
|
||||||
|
size="small"
|
||||||
|
color={colors.primary}
|
||||||
|
style={{ marginVertical: 16 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("all data : ", allData)
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<SearchInput onChange={() => { }} placeholder='Search....' filterPress={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: !prev.isShowFilter })) }} />
|
||||||
|
{isPending ? <Skelton /> : <FlatList
|
||||||
|
data={allData || []}
|
||||||
|
keyExtractor={(_, index) => index.toString()}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ paddingBottom: 200, paddingHorizontal: 10 }}
|
||||||
|
renderItem={({ item, index }) => (
|
||||||
|
<PurchaseTenderTile
|
||||||
|
item={item}
|
||||||
|
onPressDetails={() => {
|
||||||
|
navigation.navigate('TenderDetail', { item, onAddToPurchase: () => { }, routeFrom: 'PurchasedTender' });
|
||||||
|
}}
|
||||||
|
onStartBidding={() => {
|
||||||
|
Dispatch(setTender(item))
|
||||||
|
// navigation.navigate(NavigationString, { data: item })
|
||||||
|
}}
|
||||||
|
buttonEnable={isTenderActive(item)}
|
||||||
|
isTenderEnd={isTenderEnd(item)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
onEndReached={loadMore}
|
||||||
|
onEndReachedThreshold={0.6}
|
||||||
|
ListFooterComponent={renderFooter}
|
||||||
|
/>}
|
||||||
|
|
||||||
|
|
||||||
|
{allBoolean.isShowFilter && <View style={{ flex: 1 }}>
|
||||||
|
<FilterModal visible={allBoolean.isShowFilter} onClose={() => { setAllBoolean((prev: any) => ({ ...prev, isShowFilter: false })) }} onApply={() => { }} />
|
||||||
|
</View>}
|
||||||
|
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PurchaseTenderAllList;
|
||||||
|
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
|
import Animated, { FadeIn, FadeOut, Layout, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
|
||||||
|
import { ChevronDown, Trash2, Info } from 'lucide-react-native';
|
||||||
|
import { InsideHeader } from '../../Component/Index';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { togglePurchaseAuction } from '../../Redux/Slices/selectPurchaseAuction';
|
||||||
|
|
||||||
|
const PurchaseTenderList = () => {
|
||||||
|
const Navigation: any = useNavigation()
|
||||||
|
const Dispatch = useDispatch()
|
||||||
|
const [expandedIndex, setExpandedIndex] = useState<number | null>(null);
|
||||||
|
const SelectedPurchaseList = useSelector((state: any) => state.selectPurchaseTender);
|
||||||
|
const toggleExpand = (index: number) => {
|
||||||
|
setExpandedIndex(prev => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
console.log("SelectedPurchaseList : ", SelectedPurchaseList)
|
||||||
|
return (
|
||||||
|
<View style={styles.screen}>
|
||||||
|
<InsideHeader title="Purchase" showArrow />
|
||||||
|
|
||||||
|
<ScrollView contentContainerStyle={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.heading}>Tender Purchase List</Text>
|
||||||
|
<Info size={20} color="#0F172A" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Cards */}
|
||||||
|
{SelectedPurchaseList.map((item: any, index: any) => {
|
||||||
|
const expanded = expandedIndex === index;
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
key={item.auctionId}
|
||||||
|
layout={Layout.springify()}
|
||||||
|
style={styles.card}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={0.9}
|
||||||
|
onPress={() => toggleExpand(index)}
|
||||||
|
>
|
||||||
|
<CardHeader
|
||||||
|
title={item.auctionOfWork}
|
||||||
|
expanded={expanded}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text style={styles.subTitle}>
|
||||||
|
Tender No: {item.tenderNumber}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text style={styles.subTitle}>
|
||||||
|
Status:{' '}
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: item.tenderStatus === 'Opened' ? '#16A34A' : '#dc6f26ff',
|
||||||
|
fontWeight: '700',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.tenderStatus === 'Opened' ? 'Open' : 'Approved'}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{/* Expanded Content */}
|
||||||
|
{expanded && (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeIn.duration(220)}
|
||||||
|
exiting={FadeOut.duration(180)}
|
||||||
|
layout={Layout.springify()}
|
||||||
|
style={styles.details}
|
||||||
|
>
|
||||||
|
{/* {renderRow('', item.departmentName)} */}
|
||||||
|
{/* {renderRow('Pattern', item.auctionPattern)} */}
|
||||||
|
{renderRow('Fees', `₹ ${item.documentFees}`)}
|
||||||
|
{renderRow('Department', item.departmentName)}
|
||||||
|
{renderRow('Purchase End', item.purchaseEnd)}
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.removeButton} onPress={() => { Dispatch(togglePurchaseAuction(item)) }}>
|
||||||
|
<Trash2 size={18} color="#fff" />
|
||||||
|
<Text style={styles.removeText}>Remove</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<TouchableOpacity disabled={Boolean(!SelectedPurchaseList.length)} style={[styles.proceedButton, { backgroundColor: Boolean(!SelectedPurchaseList.length) ? colors.lightText : colors.primary }]} onPress={() => { Navigation.navigate(NavigationString.ProceedToPurTender) }}>
|
||||||
|
<Text style={styles.proceedText}>Proceed to Purchase</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PurchaseTenderList;
|
||||||
|
|
||||||
|
const CardHeader = ({ title, expanded }: {
|
||||||
|
title: string;
|
||||||
|
expanded: boolean;
|
||||||
|
}) => {
|
||||||
|
const rotation = useSharedValue(expanded ? 1 : 0);
|
||||||
|
rotation.value = withTiming(expanded ? 1 : 0, { duration: 220 });
|
||||||
|
const arrowStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ rotate: `${rotation.value * 180}deg` }],
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
<Animated.View style={arrowStyle}>
|
||||||
|
<ChevronDown size={22} color={colors.black} />
|
||||||
|
</Animated.View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRow = (label: string, value: string) => (
|
||||||
|
<View style={styles.row}>
|
||||||
|
<Text style={styles.label}>{label}</Text>
|
||||||
|
<Text style={styles.value}>{value}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
screen: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: '#F8FAFC',
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
padding: 16,
|
||||||
|
paddingBottom: 110,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 14,
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: '800',
|
||||||
|
color: colors.black,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 18,
|
||||||
|
padding: 16,
|
||||||
|
marginBottom: 14,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#E5E7EB',
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#0F172A',
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
subTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#64748B',
|
||||||
|
marginTop: 6,
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
marginTop: 16,
|
||||||
|
paddingTop: 14,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#E5E7EB',
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
marginVertical: 6,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: '#64748B',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#0F172A',
|
||||||
|
},
|
||||||
|
removeButton: {
|
||||||
|
marginTop: 18,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderRadius: 14,
|
||||||
|
paddingVertical: 12,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
},
|
||||||
|
removeText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '700',
|
||||||
|
},
|
||||||
|
proceedButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 16,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
paddingVertical: 16,
|
||||||
|
borderRadius: 18,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
proceedText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '800',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
Platform,
|
||||||
|
} from 'react-native';
|
||||||
|
import Animated, {
|
||||||
|
FadeInUp,
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withTiming,
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
import moment from 'moment';
|
||||||
|
import CountdownTimer from '../../Component/Counter/CountdownTimer';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
|
||||||
|
const PurchaseTenderTile = ({
|
||||||
|
item,
|
||||||
|
onPressDetails,
|
||||||
|
onStartPurchase,
|
||||||
|
}: any) => {
|
||||||
|
const scale = useSharedValue(1);
|
||||||
|
const elevation = useSharedValue(8);
|
||||||
|
|
||||||
|
const animatedStyle = useAnimatedStyle(() => ({
|
||||||
|
transform: [{ scale: scale.value }],
|
||||||
|
elevation: elevation.value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handlePressIn = () => {
|
||||||
|
scale.value = withTiming(0.97, { duration: 120 });
|
||||||
|
elevation.value = withTiming(3, { duration: 120 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePressOut = () => {
|
||||||
|
scale.value = withTiming(1, { duration: 160 });
|
||||||
|
elevation.value = withTiming(8, { duration: 160 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLive = item?.liveStatus === true;
|
||||||
|
|
||||||
|
const statusColor = isLive ? colors.primary : '#94A3B8';
|
||||||
|
const lightStatusBg = isLive ? '#EFF6FF' : '#F8FAFC';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animated.View
|
||||||
|
entering={FadeInUp.duration(400)}
|
||||||
|
style={[styles.shadowContainer, animatedStyle]}
|
||||||
|
>
|
||||||
|
<TouchableOpacity
|
||||||
|
activeOpacity={1}
|
||||||
|
onPress={onPressDetails}
|
||||||
|
onPressIn={handlePressIn}
|
||||||
|
onPressOut={handlePressOut}
|
||||||
|
style={styles.cardInternal}
|
||||||
|
>
|
||||||
|
<View style={[styles.statusAccent, { backgroundColor: statusColor }]} />
|
||||||
|
|
||||||
|
<View style={styles.mainInfo}>
|
||||||
|
{/* Header */}
|
||||||
|
<View style={styles.headerRow}>
|
||||||
|
<View>
|
||||||
|
<View style={[styles.statusBadge, { backgroundColor: lightStatusBg }]}>
|
||||||
|
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
|
||||||
|
<Text style={[styles.statusText, { color: statusColor }]}>
|
||||||
|
{isLive ? 'LIVE' : 'CLOSED'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.title}>{item?.tenderNumber}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.amountContainer}>
|
||||||
|
<Text style={styles.amount}>
|
||||||
|
₹{Number(item?.tenderFeeInFig || 0).toLocaleString('en-IN')}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.feeLabel}>Tender Fee</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Date */}
|
||||||
|
<View style={styles.dateRow}>
|
||||||
|
<Text style={styles.dateText}>
|
||||||
|
<Text style={styles.dateLabel}>Purchase Ends: </Text>
|
||||||
|
{moment(
|
||||||
|
item?.tenderKeyDatesAmendment?.tenderPurchaseDate?.endDateTime,
|
||||||
|
'DD-MM-YYYY HH:mm:ss'
|
||||||
|
).format('DD MMM, hh:mm A')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<View style={styles.footer}>
|
||||||
|
<View style={styles.footerTop}>
|
||||||
|
<Text style={styles.footerLabel}>Remaining Time</Text>
|
||||||
|
|
||||||
|
{isLive ? (
|
||||||
|
<CountdownTimer
|
||||||
|
endDate={
|
||||||
|
item?.tenderKeyDatesAmendment?.tenderPurchaseDate?.endDateTime
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Text style={styles.endedText}>--:--:--</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
disabled={!isLive}
|
||||||
|
onPress={onStartPurchase}
|
||||||
|
activeOpacity={0.85}
|
||||||
|
style={[
|
||||||
|
styles.actionButton,
|
||||||
|
{ backgroundColor: statusColor },
|
||||||
|
!isLive && styles.disabledButton,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text style={styles.addText}>
|
||||||
|
{isLive ? 'Proceed to Purchase' : 'Tender Closed'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</Animated.View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PurchaseTenderTile;
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
shadowContainer: {
|
||||||
|
marginHorizontal: 5,
|
||||||
|
marginTop: 14,
|
||||||
|
marginBottom: 6,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
elevation: 8,
|
||||||
|
},
|
||||||
|
cardInternal: {
|
||||||
|
borderRadius: 16,
|
||||||
|
overflow: 'hidden',
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
},
|
||||||
|
statusAccent: {
|
||||||
|
width: 5,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
mainInfo: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 16,
|
||||||
|
},
|
||||||
|
headerRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
statusBadge: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingHorizontal: 8,
|
||||||
|
paddingVertical: 3,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginBottom: 6,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
|
statusDot: {
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
borderRadius: 3,
|
||||||
|
marginRight: 5,
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontWeight: '800',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#1E293B',
|
||||||
|
},
|
||||||
|
amountContainer: {
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
fontWeight: '900',
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#0F172A',
|
||||||
|
},
|
||||||
|
feeLabel: {
|
||||||
|
fontSize: 10,
|
||||||
|
color: '#64748B',
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
dateRow: {
|
||||||
|
backgroundColor: '#F8FAFC',
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderRadius: 10,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#F1F5F9',
|
||||||
|
},
|
||||||
|
dateText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#334155',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
dateLabel: {
|
||||||
|
fontWeight: '400',
|
||||||
|
color: '#64748B',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#F1F5F9',
|
||||||
|
paddingTop: 12,
|
||||||
|
},
|
||||||
|
footerTop: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
footerLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#94A3B8',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
},
|
||||||
|
endedText: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#CBD5E1',
|
||||||
|
},
|
||||||
|
actionButton: {
|
||||||
|
width: '100%',
|
||||||
|
height: 48,
|
||||||
|
borderRadius: 12,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
...Platform.select({
|
||||||
|
ios: {
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
disabledButton: {
|
||||||
|
backgroundColor: '#E2E8F0',
|
||||||
|
},
|
||||||
|
addText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: '800',
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,12 +1,165 @@
|
|||||||
import { View, Text } from 'react-native'
|
import React, { useState, useRef } from 'react';
|
||||||
import React from 'react'
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
Animated,
|
||||||
|
Dimensions,
|
||||||
|
} from 'react-native';
|
||||||
|
import { InsideHeader } from '../../Component/Index';
|
||||||
|
import {
|
||||||
|
useFetchLiveTender,
|
||||||
|
useFetchPurchaseTender,
|
||||||
|
} from '../../Services/BBPS/Hooks';
|
||||||
|
import colors from '../../Constant/Color';
|
||||||
|
import { ShoppingCart } from 'lucide-react-native';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import NavigationString from '../../Constant/NavigationString';
|
||||||
|
import FontsFamily from '../../Constant/FontsFamily';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import LiveTender from './LiveTender';
|
||||||
|
import PurchaseTenderAllList from './PurchaseTenderAllList';
|
||||||
|
const { width } = Dimensions.get('screen')
|
||||||
|
const TAB_WIDTH = 140;
|
||||||
|
const TAB_HEIGHT = 44;
|
||||||
|
|
||||||
const Tender = () => {
|
const Tender = () => {
|
||||||
return (
|
const Navigation = useNavigation() as any
|
||||||
<View>
|
const { data: liveTenderData = {} }: any = useFetchLiveTender();
|
||||||
<Text>Tender</Text>
|
const { data: purchaseTenderData = {} } : any = useFetchPurchaseTender();
|
||||||
</View>
|
const SelectedPurchaseList = useSelector((state: any) => state?.selectPurchaseTender);
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tender
|
|
||||||
|
console.log("liveTenderData : ", liveTenderData)
|
||||||
|
console.log("purchaseTenderData : ", purchaseTenderData)
|
||||||
|
const liveCount = liveTenderData?.totalCount || 0;
|
||||||
|
|
||||||
|
const purchaseCount = purchaseTenderData?.totalCount || 0;
|
||||||
|
const [selectedTab, setSelectedTab] = useState<'live' | 'purchase'>('live');
|
||||||
|
|
||||||
|
const translateX = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
|
const onTabPress = (tab: 'live' | 'purchase', index: number) => {
|
||||||
|
setSelectedTab(tab);
|
||||||
|
Animated.spring(translateX, {
|
||||||
|
toValue: index * TAB_WIDTH,
|
||||||
|
useNativeDriver: true,
|
||||||
|
friction: 8,
|
||||||
|
}).start();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<InsideHeader title="Tender" showArrow />
|
||||||
|
|
||||||
|
{/* Switch Tabs */}
|
||||||
|
<View style={{ flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center' }}>
|
||||||
|
<View style={styles.switchWrapper}>
|
||||||
|
<Animated.View
|
||||||
|
pointerEvents="none"
|
||||||
|
style={[
|
||||||
|
styles.activeTab,
|
||||||
|
{
|
||||||
|
transform: [{ translateX }],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.tab}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
onPress={() => onTabPress('live', 0)}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.tabText,
|
||||||
|
selectedTab === 'live' && styles.activeText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Live ({liveCount})
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.tab}
|
||||||
|
activeOpacity={0.8}
|
||||||
|
onPress={() => onTabPress('purchase', 1)}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.tabText,
|
||||||
|
selectedTab === 'purchase' && styles.activeText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
Purchased ({purchaseCount})
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
{/* Cart */}
|
||||||
|
<View>
|
||||||
|
{SelectedPurchaseList.length && <View style={{ borderWidth: 2, borderColor: colors.white, backgroundColor: colors.primaryLight, position: 'absolute', borderRadius: 50, zIndex: 20, right: 0, top: -10, paddingHorizontal: 2 }}>
|
||||||
|
<Text style={{ fontSize: 12, color: colors.black }}>{SelectedPurchaseList.length}</Text>
|
||||||
|
</View>}
|
||||||
|
<TouchableOpacity style={{ padding: 10, backgroundColor: colors.primaryLight, borderRadius: 20 }} onPress={() => { Navigation.navigate(NavigationString.PurchaseTenderList) }}>
|
||||||
|
<ShoppingCart size={22} color={colors.black} strokeWidth={2} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{selectedTab === 'live' ? <LiveTender /> : <PurchaseTenderAllList />}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tender;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- Switch UI ---------- */
|
||||||
|
|
||||||
|
switchWrapper: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginVertical: 14,
|
||||||
|
backgroundColor: '#f1f5f9',
|
||||||
|
borderRadius: 18,
|
||||||
|
padding: 2,
|
||||||
|
height: TAB_HEIGHT,
|
||||||
|
},
|
||||||
|
|
||||||
|
tab: {
|
||||||
|
width: TAB_WIDTH,
|
||||||
|
height: TAB_HEIGHT - 4,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
tabText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#64748b',
|
||||||
|
lineHeight: 18,
|
||||||
|
fontFamily: FontsFamily.poppinsSemiBold
|
||||||
|
},
|
||||||
|
|
||||||
|
activeText: {
|
||||||
|
color: colors.primary,
|
||||||
|
},
|
||||||
|
|
||||||
|
activeTab: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 2,
|
||||||
|
left: 2,
|
||||||
|
width: TAB_WIDTH,
|
||||||
|
height: TAB_HEIGHT - 4,
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export interface LiveTenderTileProps {
|
||||||
|
data: any
|
||||||
|
onPurchasePress?: () => void;
|
||||||
|
onDetailsPress?: () => void;
|
||||||
|
index: number;
|
||||||
|
tenderDispatchDate?: string;
|
||||||
|
tenderOfWork?: string;
|
||||||
|
purchaseEnd?: string;
|
||||||
|
tenderFees?: string | number;
|
||||||
|
isPurchased?: boolean;
|
||||||
|
tenderId?: any;
|
||||||
|
tenderNumber?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface allBooleanProps {
|
||||||
|
showDetail?: boolean
|
||||||
|
isShowFilter?: boolean
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ import Home from "./Home/Home";
|
|||||||
import Login from "./Login/Login";
|
import Login from "./Login/Login";
|
||||||
import Onboarding from "./Onboarding/Onboarding";
|
import Onboarding from "./Onboarding/Onboarding";
|
||||||
import Splash from "./Splash/Splash";
|
import Splash from "./Splash/Splash";
|
||||||
|
import ProceedToPurTender from "./Tender/ProceedToPurTender";
|
||||||
|
import PurchaseTenderList from "./Tender/PurchaseTenderList";
|
||||||
import Tender from "./Tender/Tender";
|
import Tender from "./Tender/Tender";
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ const Screens = {
|
|||||||
PurchaseList,
|
PurchaseList,
|
||||||
Emd,
|
Emd,
|
||||||
StartBidding,
|
StartBidding,
|
||||||
PurchaseAuctionList
|
PurchaseAuctionList,
|
||||||
|
PurchaseTenderList,
|
||||||
|
ProceedToPurTender
|
||||||
}
|
}
|
||||||
export default Screens
|
export default Screens
|
||||||
@@ -32,6 +32,7 @@ const PROCEED_PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/proceedToPurcha
|
|||||||
const PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuction`
|
const PURCHASE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuction`
|
||||||
const PURCHASE_OFFLINE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuctionOffline`
|
const PURCHASE_OFFLINE_AUCTION = `${BASE_URL_Auction}api/auction/purchaseAuctionOffline`
|
||||||
const PURCHASE_TENDER = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTender`
|
const PURCHASE_TENDER = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTender`
|
||||||
|
const PURCHASE_TENDER_OFFLINE = `https://procurelinc.in/EProcurementSB/tenderPayment/purchaseTenderOffline`
|
||||||
const AUCTION_PURCHASE_STATUS = `https://api.godemo.in/Auction/api/auction/transactionStatus?orderNo=`
|
const AUCTION_PURCHASE_STATUS = `https://api.godemo.in/Auction/api/auction/transactionStatus?orderNo=`
|
||||||
const GET_TENDER_DETAILS = `${BASE_URL}eTendering/getTenderById?tenderId=`
|
const GET_TENDER_DETAILS = `${BASE_URL}eTendering/getTenderById?tenderId=`
|
||||||
const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=`
|
const GET_DOCID = `${BASE_URL}eTendering/download/docId?docId=`
|
||||||
@@ -62,6 +63,11 @@ const ITEM_DOCUMENT_DOWNLOAD = `${BASE_URL_Auction}api/auction/home/downloadDocu
|
|||||||
// Rohit
|
// Rohit
|
||||||
const DASHBOARD_COUNT = `${BASE_URL_Auction}api/auction/home/dashboard/counts`
|
const DASHBOARD_COUNT = `${BASE_URL_Auction}api/auction/home/dashboard/counts`
|
||||||
|
|
||||||
|
//tender
|
||||||
|
|
||||||
|
const TENDER_LOGIN = `${BASE_URL}eproc/loginWithoutDecryption`
|
||||||
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Rohit
|
// Rohit
|
||||||
DASHBOARD_COUNT,
|
DASHBOARD_COUNT,
|
||||||
@@ -118,6 +124,8 @@ export {
|
|||||||
AUCTION_LOGIN,
|
AUCTION_LOGIN,
|
||||||
TWO_FACTOR_AUTH,
|
TWO_FACTOR_AUTH,
|
||||||
GET_MASTER_DATA,
|
GET_MASTER_DATA,
|
||||||
PURCHASE_OFFLINE_AUCTION
|
PURCHASE_OFFLINE_AUCTION,
|
||||||
|
TENDER_LOGIN,
|
||||||
|
PURCHASE_TENDER_OFFLINE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { fetchDashboard, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseTender, offlineAuctionPurchase, userLogin } from './apis';
|
import { fetchDashboard, fetchLiveAuction, fetchLiveTender, fetchPurchaseAuction, fetchPurchaseTender, offlineAuctionPurchase, userLogin, offlineTenderPurchase, fetchTenderById } from './apis';
|
||||||
|
|
||||||
// User
|
// User
|
||||||
|
|
||||||
@@ -19,23 +19,45 @@ export const useDashboard = () => {
|
|||||||
|
|
||||||
// Auction & Tender
|
// Auction & Tender
|
||||||
|
|
||||||
export const useFetchPurchaseTender = () => {
|
export const useFetchPurchaseTender = (size: number) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [],
|
queryKey: ['purchaseTender', size],
|
||||||
queryFn: () => fetchPurchaseTender()
|
queryFn: () => fetchPurchaseTender({ page: 0, size }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useFetchLiveTender = () => {
|
export const useFetchLiveTender = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [],
|
queryKey: ['fetchLiveTender',],
|
||||||
queryFn: () => fetchLiveTender()
|
queryFn: () => fetchLiveTender()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const useFetchPurchaseAuction = () => {
|
export const useFetchPurchaseAuction = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['fetchPurchaseAuction',],
|
queryKey: ['fetchPurchaseAuction',],
|
||||||
queryFn: () => fetchPurchaseAuction()
|
queryFn: ({ pageParam = 1 }) =>
|
||||||
|
fetchPurchaseAuction({
|
||||||
|
page: pageParam,
|
||||||
|
size: 10,
|
||||||
|
}),
|
||||||
|
getNextPageParam: (lastPage: any) => {
|
||||||
|
const { page, size, totalCount } = lastPage;
|
||||||
|
const totalPages = Math.ceil(totalCount / size);
|
||||||
|
const nextPage = page + 1;
|
||||||
|
return nextPage < totalPages ? nextPage : undefined;
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
|
||||||
|
const mergedResults = data.pages.flatMap((page: any) => page.data ?? []);
|
||||||
|
|
||||||
|
console.log("mergedResults for tender : ", mergedResults)
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
result: mergedResults,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,4 +96,18 @@ export const useOfflineAuctionPurchase = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (payload: any) => offlineAuctionPurchase(payload),
|
mutationFn: (payload: any) => offlineAuctionPurchase(payload),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
export const useOfflineTenderPurchase = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (payload: any) => offlineTenderPurchase(payload),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFetchTenderById = (tenderId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['fetchTenderById', tenderId],
|
||||||
|
queryFn: () => fetchTenderById(tenderId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+58
-14
@@ -1,12 +1,13 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { DASHBOARD_COUNT, GET_MY_AUCTION, GET_MY_TENDER, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION } from "./ApiUrls";
|
import { DASHBOARD_COUNT, GET_MY_AUCTION, GET_MY_TENDER, GET_TENDER_DETAILS, LIVE_AUCTION, LIVE_TENDER, LOG_IN_URL, PROCEED_PURCHASE_AUCTION, PURCHASE_TENDER_OFFLINE, TENDER_LOGIN } from "./ApiUrls";
|
||||||
import { apiCall } from "../../Axios/Axios";
|
import { apiCall } from "../../Axios/Axios";
|
||||||
|
|
||||||
// User
|
// User
|
||||||
|
|
||||||
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
|
export const userLogin = async (payload: { emailAdd: string, password: string }): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(LOG_IN_URL, { ...payload, divice: "mobileApp" });
|
// const response = await axios.post(LOG_IN_URL, { ...payload, divice: "mobileApp" });
|
||||||
|
const response = await axios.post(TENDER_LOGIN, { ...payload, device: "mobileApp" });
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw error.response?.data || error.message;
|
throw error.response?.data || error.message;
|
||||||
@@ -18,17 +19,8 @@ export const fetchDashboard = async (): Promise<any> => {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchPurchaseAuction = async (payload: any): Promise<any> => {
|
||||||
export const fetchPurchaseTender = async (): Promise<any> => {
|
const data = await apiCall<any>('get', `${GET_MY_AUCTION}`, {}, { payload });
|
||||||
const data = await apiCall<any>('get', `${GET_MY_TENDER}`);
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
export const fetchLiveTender = async (): Promise<any> => {
|
|
||||||
const data = await apiCall<any>('get', `${LIVE_TENDER}`);
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
export const fetchPurchaseAuction = async (): Promise<any> => {
|
|
||||||
const data = await apiCall<any>('get', `${GET_MY_AUCTION}`);
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
export const fetchLiveAuction = async (payload: any): Promise<any> => {
|
export const fetchLiveAuction = async (payload: any): Promise<any> => {
|
||||||
@@ -44,4 +36,56 @@ export const offlineAuctionPurchase = async (payload: any): Promise<any> => {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw error.response?.data || error.message;
|
throw error.response?.data || error.message;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Tender Purchase offline
|
||||||
|
export const offlineTenderPurchase = async (payload: any): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const response = await apiCall<any>('post', PURCHASE_TENDER_OFFLINE, payload);
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
throw error.response?.data || error.message;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// tender
|
||||||
|
export const fetchPurchaseTender = async (payload: any): Promise<any> => {
|
||||||
|
const data = await apiCall<any>(
|
||||||
|
'get',
|
||||||
|
`${GET_MY_TENDER}`, {},
|
||||||
|
{ payload }
|
||||||
|
);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// export const fetchPurchaseTender = async (
|
||||||
|
// page: number,
|
||||||
|
// size: number
|
||||||
|
// ): Promise<any> => {
|
||||||
|
// const data = await apiCall<any>(
|
||||||
|
// 'get',
|
||||||
|
// `${GET_MY_TENDER}?page=0&size=${size}`
|
||||||
|
// );
|
||||||
|
|
||||||
|
// console.log('Purchased Tenders Response:', data);
|
||||||
|
// return data;
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
export const fetchLiveTender = async (): Promise<any> => {
|
||||||
|
const data = await apiCall<any>('get', `${LIVE_TENDER}`);
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
export const fetchTenderById = async (tenderId: string): Promise<any> => {
|
||||||
|
console.log('Fetching tender with ID:', tenderId);
|
||||||
|
|
||||||
|
const data = await apiCall<any>(
|
||||||
|
'get',
|
||||||
|
`https://procurelinc.in/EProcurementSB/eTendering/getTenderById?tenderId=${tenderId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('API response:', data);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user