From 458762e1799fd5e61f2a2a44d5efee5acae4c5ab Mon Sep 17 00:00:00 2001 From: Lucia Dabezies Date: Thu, 4 Jul 2024 12:05:49 +0200 Subject: [PATCH 1/6] Add acceptance offer motivation modal --- package-lock.json | 4 +- src/components/TenderRequest.tsx | 187 ++++++++++++++++++++++--------- src/data/offers.ts | 1 + 3 files changed, 140 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index faf27a3..32c4288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "foodmarket", + "name": "skaff", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "foodmarket", + "name": "skaff", "version": "1.0.0", "dependencies": { "@expo/webpack-config": "^18.0.1", diff --git a/src/components/TenderRequest.tsx b/src/components/TenderRequest.tsx index 33d1700..0368b16 100644 --- a/src/components/TenderRequest.tsx +++ b/src/components/TenderRequest.tsx @@ -12,7 +12,14 @@ import { List, Avatar, } from 'react-native-paper' -import { ScrollView, StyleSheet, View } from 'react-native' +import { + Modal, + ScrollView, + StyleSheet, + TextInput, + TouchableOpacity, + View, +} from 'react-native' import { Tabs, TabScreen } from 'react-native-paper-tabs' import Chat from './Chat' import useTenderRequests from '../hooks/useTenderRequests' @@ -45,6 +52,10 @@ const TenderRequest = ({ const { user } = useAuth() const [offers, updateOffer, , refreshOffers] = useOffers() + const [showModal, setShowModal] = useState(false) + const [acceptanceMotivationText, setAcceptanceMotivationText] = useState(""); + + useEffect(() => { if (route.params.tenderRequestId) { const tenderRequestFromState = tenderRequests.find( @@ -256,68 +267,108 @@ const TenderRequest = ({ Matchande anbud {validOffers.map((offer, i) => ( - navigation.navigate('TenderRequest', { id })} - > - { - if (offer.approved) - return ( - - - + navigation.navigate('TenderRequest', { id })} + > + { + if (offer.approved) + return ( + - Tilldelad - - + + + Tilldelad + + + ) + else if ( + offers.filter((offer) => offer.approved).length < + 1 ) - else if ( - offers.filter((offer) => offer.approved).length < 1 - ) - return ( + return ( + + ) + }} + /> + {offer.acceptanceMotivation && ( + + Acceptance reason: {offer.acceptanceMotivation} + + )} + + + + + + Motivation to choose {offer.buyer.name}'s offer:{' '} + + setAcceptanceMotivationText(text)} + /> + + - ) - }} - /> - + + + + + ))} + Ej uppfyllda anbud @@ -368,4 +419,40 @@ const styles = StyleSheet.create({ marginBottom: 5, backgroundColor: 'white', }, + centerView: { + backgroundColor: 'rgba(0,0,0,0.5)', + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + modalView: { + backgroundColor: 'white', + marginHorizontal: 20, + padding: 35, + borderRadius: 4, + }, + modalText: { + fontSize: 16, + marginBottom: 12, + }, + modalInput: { + height: 120, + textAlignVertical: 'top', + margin: 12, + borderWidth: 0.3, + padding: 10, + borderRadius: 4, + }, + modalButtons: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + }, + acceptanceReasonTextWrapper: { + paddingHorizontal: 12, + paddingBottom: 12 + }, + acceptanceReasonText: { + fontSize: 12, + } }) diff --git a/src/data/offers.ts b/src/data/offers.ts index 72824bb..8601ceb 100644 --- a/src/data/offers.ts +++ b/src/data/offers.ts @@ -9,6 +9,7 @@ export type Offer = { buyer: Buyer supplier: Supplier approved: boolean + acceptanceMotivation: string submissionDate: Date submitted: boolean tenderRequestId: string From 4ba92ac445980d29375eb3e160fdcb79de7cff4f Mon Sep 17 00:00:00 2001 From: Lucia Dabezies Date: Thu, 4 Jul 2024 12:06:15 +0200 Subject: [PATCH 2/6] Send notifications to everyone who bid --- api/server.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/api/server.ts b/api/server.ts index 0fcecda..148c28b 100644 --- a/api/server.ts +++ b/api/server.ts @@ -241,6 +241,9 @@ io.on('connection', (socket) => { const tenderRequest = state.tenderRequests.find( (tr) => tr.id === offer.tenderRequestId ) + const otherOffersForTender = state.offers.filter( + (o) => o.tenderRequestId === tenderRequest?.id && offer.id !== o.id + ) if (tenderRequest) sendPushNotification({ to: [token], @@ -254,6 +257,22 @@ io.on('connection', (socket) => { tenderRequestId: offer.tenderRequestId, }, }) + + if(tenderRequest && otherOffersForTender) + otherOffersForTender.forEach(o => + sendPushNotification({ + to: [o.supplier.token], + title: 'Anbud förkastat', + body: `Ditt bud på ${tenderRequest.title} har förkastats. ${offer.supplier.name}s bud har godkänts av följande skäl: ${offer.acceptanceMotivation}`, + data: { + date: new Date(), + type: 'offer', + to: [o.supplier.id], + id: offer.id, + tenderRequestId: tenderRequest?.id, + }, + }) + ) } state.offers[index] = offer io.emit('offers', state.offers) From ba1bc7f7412b49e762fca174afa57a4ed71fa0eb Mon Sep 17 00:00:00 2001 From: Lucia Dabezies Date: Fri, 5 Jul 2024 09:24:08 +0200 Subject: [PATCH 3/6] Format fix --- api/server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/server.ts b/api/server.ts index 148c28b..e84a2ac 100644 --- a/api/server.ts +++ b/api/server.ts @@ -258,7 +258,7 @@ io.on('connection', (socket) => { }, }) - if(tenderRequest && otherOffersForTender) + if (tenderRequest && otherOffersForTender) otherOffersForTender.forEach(o => sendPushNotification({ to: [o.supplier.token], @@ -272,8 +272,8 @@ io.on('connection', (socket) => { tenderRequestId: tenderRequest?.id, }, }) - ) - } + ) + } state.offers[index] = offer io.emit('offers', state.offers) }) From 2a08a568b1383516f46c12dc3f6129e3b33ff7bf Mon Sep 17 00:00:00 2001 From: Christina Sonebo Date: Tue, 13 Aug 2024 15:44:44 +0200 Subject: [PATCH 4/6] fix: use offer.id instead of list index as key --- src/components/TenderRequest.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/TenderRequest.tsx b/src/components/TenderRequest.tsx index 0368b16..19c3fb5 100644 --- a/src/components/TenderRequest.tsx +++ b/src/components/TenderRequest.tsx @@ -218,9 +218,9 @@ const TenderRequest = ({ )} Dina skickade anbud - {validOffers.map((offer, i) => ( + {validOffers.map((offer) => ( { console.log('pressed', offer) @@ -266,10 +266,10 @@ const TenderRequest = ({ Matchande anbud - {validOffers.map((offer, i) => ( + {validOffers.map((offer) => ( navigation.navigate('TenderRequest', { id })} > From f41835d153ab7ddb2a8f1395de03e5a995625806 Mon Sep 17 00:00:00 2001 From: Christina Sonebo Date: Wed, 14 Aug 2024 11:24:11 +0200 Subject: [PATCH 5/6] feat: make useOffers hook return all offers if no supplier or buyer specified --- src/hooks/useOffers.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hooks/useOffers.ts b/src/hooks/useOffers.ts index 71f890a..b1038a9 100644 --- a/src/hooks/useOffers.ts +++ b/src/hooks/useOffers.ts @@ -26,17 +26,16 @@ const useOffers = (): [Array, any, any, any] => { } const refresh = ({ supplier, buyer } = {}) => - socket.emit('offers', (offers: Array) => - setOffers( - offers - .filter((o) => { - if (supplier) return o.supplier.id == supplier.id - if (buyer) return o.buyer.id == buyer.id - return true - }) - .sort((a, b) => a.price.SEK - b.price.SEK) - ) - ) + socket.emit('offers', (offers: Array) => { + let filteredOffers = offers + if (supplier) { + filteredOffers = filteredOffers.filter((o) => o.supplier.id == supplier.id) + } + if (buyer) { + filteredOffers = filteredOffers.filter((o) => o.buyer.id == buyer.id) + } + setOffers(filteredOffers.sort((a, b) => a.price.SEK - b.price.SEK)) + }) return [offers, editOffer, addOffer, refresh] } From 7a5a08e8d52f8e30392dd9105917f4a41cf1d701 Mon Sep 17 00:00:00 2001 From: Christina Sonebo Date: Wed, 14 Aug 2024 14:09:40 +0200 Subject: [PATCH 6/6] feat: show the acceptance reason in rejected offers --- src/components/TenderRequest.tsx | 133 ++++++++++++++++++------------- 1 file changed, 76 insertions(+), 57 deletions(-) diff --git a/src/components/TenderRequest.tsx b/src/components/TenderRequest.tsx index 19c3fb5..023c5d7 100644 --- a/src/components/TenderRequest.tsx +++ b/src/components/TenderRequest.tsx @@ -54,7 +54,7 @@ const TenderRequest = ({ const [showModal, setShowModal] = useState(false) const [acceptanceMotivationText, setAcceptanceMotivationText] = useState(""); - + const [selectedOffer, setSelectedOffer] = useState(undefined) useEffect(() => { if (route.params.tenderRequestId) { @@ -68,9 +68,7 @@ const TenderRequest = ({ }, [route.params.tenderRequestId, tenderRequests]) useEffect(() => { - const buyer = user?.type === 'buyer' ? user : undefined - const supplier = user?.type === 'supplier' ? user : undefined - refreshOffers({ buyer, supplier }) + refreshOffers() refreshTenderRequests() }, [user]) @@ -81,11 +79,15 @@ const TenderRequest = ({ ) - const validOffers = offers + + const buyer = user?.type === 'buyer' ? user : undefined + const supplier = user?.type === 'supplier' ? user : undefined + + const myValidOffers = offers .filter((offer) => offer.tenderRequestId === tenderRequest?.id) .filter( (offer) => offer.buyer.id === user?.id || offer.supplier.id === user?.id - ) + ).filter(buyer ? (offer) => offer.buyer.id === buyer.id : (offer) => offer.supplier.id === supplier?.id) .sort((a, b) => a.price.SEK - b.price.SEK) return ( @@ -102,10 +104,10 @@ const TenderRequest = ({ style={{ backgroundColor: '#D8F5E3' }} // dark={false} // works the same as AppBar in react-native-paper theme={theme} // works the same as AppBar in react-native-paper - // mode="scrollable" // fixed, scrollable | default=fixed - // onChangeIndex={(newIndex) => {}} // react on index change - // showLeadingSpace={true} // (default=true) show leading space in scrollable tabs inside the header - // disableSwipe={false} // (default=false) disable swipe to left/right gestures + // mode="scrollable" // fixed, scrollable | default=fixed + // onChangeIndex={(newIndex) => {}} // react on index change + // showLeadingSpace={true} // (default=true) show leading space in scrollable tabs inside the header + // disableSwipe={false} // (default=false) disable swipe to left/right gestures > @@ -194,7 +196,7 @@ const TenderRequest = ({ <> {user?.type === 'supplier' && ( - {!validOffers.length && ( + {!myValidOffers.length && ( <> För att lämna anbud måste du vara ansluten till detta @@ -218,40 +220,45 @@ const TenderRequest = ({ )} Dina skickade anbud - {validOffers.map((offer) => ( - { - console.log('pressed', offer) - navigation.navigate('Supplier', { - supplier: offer.supplier, - }) - }} - > - { + const winningOffer = offers.find((offer) => offer.tenderRequestId === tenderRequest.id && offer.approved) + return ( + { + console.log('pressed', offer) + navigation.navigate('Supplier', { + supplier: offer.supplier, + }) }} - left={(props) => ( - - )} - title={offer.price.SEK + ' kr'} - subtitle={ - 'Inlämnad ' + - offer.submissionDate?.toString().split('T')[0] + - '. ' + - (offer.approved ? 'Vunnen' : 'Ej godkänt') - } - right={(props) => } - /> - - ))} + > + ( + + )} + title={offer.price.SEK + ' kr'} + subtitle={ + 'Inlämnad ' + + offer.submissionDate?.toString().split('T')[0] + + '. ' + } + right={(props) => } + /> + + {getStatusText(offer, winningOffer)} + + + ) + })} )} @@ -266,12 +273,12 @@ const TenderRequest = ({ Matchande anbud - {validOffers.map((offer) => ( + {myValidOffers.map((offer) => ( navigation.navigate('TenderRequest', { id })} + // onPress={() => navigation.navigate('TenderRequest', { id })} > { if (offer.approved) @@ -306,7 +313,7 @@ const TenderRequest = ({ marginTop: 5, }} > - Tilldelad + Tilldelad ) @@ -321,6 +328,7 @@ const TenderRequest = ({ uppercase={false} style={{ marginRight: 10 }} onPress={() => { + setSelectedOffer(offer) setShowModal(true) }} > @@ -331,7 +339,8 @@ const TenderRequest = ({ /> {offer.acceptanceMotivation && ( - Acceptance reason: {offer.acceptanceMotivation} + Motivering till val av anbud: + {offer.acceptanceMotivation} )} @@ -339,7 +348,7 @@ const TenderRequest = ({ - Motivation to choose {offer.buyer.name}'s offer:{' '} + Motivering till valet av {selectedOffer?.supplier.name}s anbud:{' '} setAcceptanceMotivationText(text)} /> - - + ))} @@ -389,6 +399,15 @@ const TenderRequest = ({ ) } +const getStatusText = (offer: Offer, winningOffer: Offer | undefined) => { + if (winningOffer != undefined) { + if (offer.approved) { + return `Ert anbud valdes med motiveringen: ${offer.acceptanceMotivation}` + } + return `Ett annat anbud från ${winningOffer.supplier.name} valdes med motiveringen: ${winningOffer.acceptanceMotivation}` + } + return 'Ej godkänt' +} const Row = ({ children }) => ( {children}