From 869e9adfdd7862feaf04d32e812edeaf2025327d Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 1 Feb 2026 12:08:24 +0000 Subject: [PATCH] Agrimatch...version1 --- .../src/components/Matches/MatchesWidget.tsx | 72 +++ frontend/src/pages/dashboard.tsx | 530 ++++-------------- frontend/src/pages/index.tsx | 315 ++++++----- 3 files changed, 345 insertions(+), 572 deletions(-) create mode 100644 frontend/src/components/Matches/MatchesWidget.tsx diff --git a/frontend/src/components/Matches/MatchesWidget.tsx b/frontend/src/components/Matches/MatchesWidget.tsx new file mode 100644 index 0000000..db7db3c --- /dev/null +++ b/frontend/src/components/Matches/MatchesWidget.tsx @@ -0,0 +1,72 @@ + +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import CardBox from '../CardBox'; +import BaseIcon from '../BaseIcon'; +import { mdiHandshake, mdiMagnify } from '@mdi/js'; +import Link from 'next/link'; + +const MatchesWidget = () => { + const [matches, setMatches] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchMatches = async () => { + try { + // Fetch matches for the user + const response = await axios.get('/matches'); + // If no real matches, show some "Potential Partners" (Agribusinesses) + if (response.data.rows && response.data.rows.length > 0) { + setMatches(response.data.rows.slice(0, 3)); + } else { + const agriResponse = await axios.get('/agribusinesses'); + setMatches(agriResponse.data.rows.slice(0, 3).map(agri => ({ + id: agri.id, + summary: `Potential match with ${agri.name}`, + status: 'potential', + agribusiness: agri + }))); + } + } catch (error) { + console.error('Error fetching matches:', error); + } finally { + setLoading(false); + } + }; + + fetchMatches(); + }, []); + + if (loading) return
Loading matches...
; + + return ( + +
+

+ + Matchmaking +

+ View all +
+
+ {matches.length > 0 ? ( + matches.map((match: any) => ( +
+
+

{match.summary || `Partner: ${match.agribusiness?.name}`}

+

{match.status || 'Active'}

+
+ + + +
+ )) + ) : ( +

No matches found yet. Post a listing to get started!

+ )} +
+
+ ); +}; + +export default MatchesWidget; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index eaef4e6..117f801 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -1,3 +1,4 @@ + import * as icon from '@mdi/js'; import Head from 'next/head' import React from 'react' @@ -14,8 +15,12 @@ import { hasPermission } from "../helpers/userPermissions"; import { fetchWidgets } from '../stores/roles/rolesSlice'; import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { SmartWidget } from '../components/SmartWidget/SmartWidget'; +import MatchesWidget from '../components/Matches/MatchesWidget'; +import CardBox from '../components/CardBox'; +import BaseButton from '../components/BaseButton'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; + const Dashboard = () => { const dispatch = useAppDispatch(); const iconsColor = useAppSelector((state) => state.style.iconsColor); @@ -24,46 +29,30 @@ const Dashboard = () => { const loadingMessage = 'Loading...'; - - const [users, setUsers] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); - const [organizations, setOrganizations] = React.useState(loadingMessage); - const [agribusinesses, setAgribusinesses] = React.useState(loadingMessage); - const [categories, setCategories] = React.useState(loadingMessage); - const [listings, setListings] = React.useState(loadingMessage); - const [matches, setMatches] = React.useState(loadingMessage); - const [conversations, setConversations] = React.useState(loadingMessage); - const [messages, setMessages] = React.useState(loadingMessage); - const [attachments, setAttachments] = React.useState(loadingMessage); - const [notifications, setNotifications] = React.useState(loadingMessage); - const [deals, setDeals] = React.useState(loadingMessage); + const [listingsCount, setListingsCount] = React.useState(loadingMessage); + const [matchesCount, setMatchesCount] = React.useState(loadingMessage); + const [messagesCount, setMessagesCount] = React.useState(loadingMessage); + const [dealsCount, setDealsCount] = React.useState(loadingMessage); - - const [widgetsRole, setWidgetsRole] = React.useState({ - role: { value: '', label: '' }, - }); const { currentUser } = useAppSelector((state) => state.auth); const { isFetchingQuery } = useAppSelector((state) => state.openAi); - const { rolesWidgets, loading } = useAppSelector((state) => state.roles); - - - const organizationId = currentUser?.organizations?.id; + + const [widgetsRole, setWidgetsRole] = React.useState({ + role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name }, + }); async function loadData() { - const entities = ['users','roles','permissions','organizations','agribusinesses','categories','listings','matches','conversations','messages','attachments','notifications','deals',]; - const fns = [setUsers,setRoles,setPermissions,setOrganizations,setAgribusinesses,setCategories,setListings,setMatches,setConversations,setMessages,setAttachments,setNotifications,setDeals,]; + const entities = ['listings', 'matches', 'messages', 'deals']; + const fns = [setListingsCount, setMatchesCount, setMessagesCount, setDealsCount]; const requests = entities.map((entity, index) => { - if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { return axios.get(`/${entity.toLowerCase()}/count`); } else { fns[index](null); return Promise.resolve({data: {count: null}}); } - }); Promise.allSettled(requests).then((results) => { @@ -80,6 +69,7 @@ const Dashboard = () => { async function getWidgets(roleId) { await dispatch(fetchWidgets(roleId)); } + React.useEffect(() => { if (!currentUser) return; loadData().then(); @@ -101,40 +91,92 @@ const Dashboard = () => { {''} + + {/* Quick Stats */} +
+ +
+ +
+
+

Listings

+

{listingsCount}

+
+
+ +
+ +
+
+

Matches

+

{matchesCount}

+
+
+ +
+ +
+
+

Messages

+

{messagesCount}

+
+
+ +
+ +
+
+

Deals

+

{dealsCount}

+
+
+
- {hasPermission(currentUser, 'CREATE_ROLES') && } - {!!rolesWidgets.length && - hasPermission(currentUser, 'CREATE_ROLES') && ( -

- {`${widgetsRole?.role?.label || 'Users'}'s widgets`} -

- )} +
+
+ +
+ +

Market Insights

+

+ Maize prices are up 12% in your region. Consider listing your harvest now to match with high-demand traders. +

+ +
+
-
- {(isFetchingQuery || loading) && ( -
- {' '} - Loading widgets... -
- )} - - { rolesWidgets && - rolesWidgets.map((widget) => ( + {/* Existing AI Widgets */} + {hasPermission(currentUser, 'CREATE_ROLES') && ( +
+ +
+ {(isFetchingQuery || loading) && ( +
+ {' '} + Loading widgets... +
+ )} + { rolesWidgets && rolesWidgets.map((widget) => ( { roleId={widgetsRole?.role?.value || ''} admin={hasPermission(currentUser, 'CREATE_ROLES')} /> - ))} -
- - {!!rolesWidgets.length &&
} - -
- - - {hasPermission(currentUser, 'READ_USERS') && -
-
-
-
- Users -
-
- {users} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ROLES') && -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_PERMISSIONS') && -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && -
-
-
-
- Organizations -
-
- {organizations} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_AGRIBUSINESSES') && -
-
-
-
- Agribusinesses -
-
- {agribusinesses} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_CATEGORIES') && -
-
-
-
- Categories -
-
- {categories} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_LISTINGS') && -
-
-
-
- Listings -
-
- {listings} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_MATCHES') && -
-
-
-
- Matches -
-
- {matches} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_CONVERSATIONS') && -
-
-
-
- Conversations -
-
- {conversations} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_MESSAGES') && -
-
-
-
- Messages -
-
- {messages} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_ATTACHMENTS') && -
-
-
-
- Attachments -
-
- {attachments} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_NOTIFICATIONS') && -
-
-
-
- Notifications -
-
- {notifications} -
-
-
- -
-
-
- } - - {hasPermission(currentUser, 'READ_DEALS') && -
-
-
-
- Deals -
-
- {deals} -
-
-
- -
-
-
- } - - -
+ ))} +
+
+ )}
) diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 1422830..3caa7c2 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,195 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import { mdiMagnify, mdiHandshake, mdiSprout, mdiTruckDelivery } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; +const featuredListings = [ + { + title: 'Dry yellow maize lot 20 tons', + type: 'demand', + location: 'Mbarema Village', + price: '$220.5/ton', + quantity: '20 tons', + }, + { + title: 'Bulk soybean demand for processing', + type: 'offer', + location: 'Coastal Town', + price: '$480.0/ton', + quantity: '10 tons', + }, + { + title: 'Fertilizer NPK 20-10-10 stock', + type: 'demand', + location: 'Riverside Market', + price: '$650.0/ton', + quantity: '50 tons', + }, +]; -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('right'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'AgriMatch' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; +export default function AgriMatchLanding() { + const textColor = useAppSelector((state) => state.style.linkColor); + const title = 'AgriMatch'; return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('The B2B Bridge for Agribusiness')} - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - - - -
+ {/* Hero Section */} +
+
+ Agriculture Field
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+
+

+ Uniting Agribusiness for a Smarter Future +

+

+ The B2B marketplace for farmers, traders, and suppliers to matchmake and grow together. +

+
+ + +
+
+ + {/* Stats/Quick Search */} +
+
+
+
+

500+

+

Verified Partners

+
+
+

1.2k

+

Active Listings

+
+
+

$2M+

+

Deals Facilitated

+
+
+
+
+ + {/* Featured Listings */} +
+
+
+
+

Featured Listings

+

Discover current needs and offers from our network

+
+ + View all + +
+
+ {featuredListings.map((listing, idx) => ( +
+
+ + {listing.type} + +

{listing.price}

+
+

{listing.title}

+
+ + {listing.location} +
+ +
+ ))} +
+
+
+ + {/* How it Works */} +
+
+

How AgriMatch Works

+
+
+
+ +
+

Create Profile

+

Tell us about your agribusiness and certifications.

+
+
+
+ +
+

List Needs

+

Post what you are selling or what you need to buy.

+
+
+
+ +
+

Get Matches

+

Our algorithm connects you with compatible partners.

+
+
+
+ +
+

Close Deals

+

Securely message and finalize terms in the app.

+
+
+
+
+ + {/* Footer */} +
+
+

{title}

+
+ Privacy Policy + Terms of Use + Admin Access +
+

© 2026 AgriMatch. All rights reserved.

+
+
); } -Starter.getLayout = function getLayout(page: ReactElement) { +AgriMatchLanding.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file