diff --git a/backend/app/urls.py b/backend/app/urls.py index c29d169..27c416f 100644 --- a/backend/app/urls.py +++ b/backend/app/urls.py @@ -2,8 +2,8 @@ from rest_framework_simplejwt.views import TokenRefreshView from rest_framework.urlpatterns import format_suffix_patterns # ✅ For better API format handling from .views import ( - CustomAuthToken, logout_view, get_employees, get_retailers,get_counts, - get_orders,get_users,get_employee_orders,get_employee_shipments,update_shipment_status,get_logged_in_user,allocate_orders, get_trucks, get_shipments,get_stock_data,category_stock_data,store_qr_code + CustomAuthToken, get_employee_id, logout_view, get_employees, get_retailers,get_counts, + get_orders,get_users,get_employee_orders,get_employee_shipments, recent_actions,update_shipment_status,get_logged_in_user,allocate_orders, get_trucks, get_shipments,get_stock_data,category_stock_data,store_qr_code ) urlpatterns = [ @@ -29,7 +29,10 @@ path('user_detail/', get_logged_in_user, name='get_logged_in_user'), path('employee_shipments/', get_employee_shipments, name='employee_shipments'), path('update_shipment_status/', update_shipment_status, name='update-shipment-status'), - path('employee_orders/', get_employee_orders, name='get_employee_orders') + path('employee_orders/', get_employee_orders, name='get_employee_orders'), + path('employee_id/', get_employee_id, name='get_employee_id'), + + path('recent_actions/', recent_actions, name='recent_actions') ] # ✅ Support API requests with format suffixes (e.g., /orders.json, /orders.xml) diff --git a/backend/app/views.py b/backend/app/views.py index 4928663..784e11b 100644 --- a/backend/app/views.py +++ b/backend/app/views.py @@ -20,6 +20,7 @@ from django.contrib.auth.models import User from django.http import JsonResponse from .permissions import IsEmployeeUser +from django.contrib.admin.models import LogEntry; # ✅ Custom Pagination Class class StandardPagination(PageNumberPagination): @@ -363,4 +364,38 @@ def get_employee_orders(request): return Response(serializer.data) def redirect_view(request): - return redirect('/admin/') \ No newline at end of file + return redirect('/admin/') + + +@api_view(['GET']) +@permission_classes([IsAuthenticated,IsEmployeeUser]) +def get_employee_id(request): + user = request.user + + try: + employee = Employee.objects.get(user=user) # Get employee linked to logged-in user + return Response({"employee_id": employee.employee_id}) # ✅ Use employee_id instead of id + except Employee.DoesNotExist: + return Response({"error": "Employee not found"}, status=404) + + +@api_view(['GET']) +@permission_classes([IsAuthenticated, IsAdminUser]) +def recent_actions(request): + # Fetch the last 10 actions performed in the admin panel + actions = LogEntry.objects.select_related('content_type', 'user').order_by('-action_time')[:10] + + # Prepare JSON response + recent_actions_list = [ + { + 'time': action.action_time, + 'user': action.user.username, + 'content_type': action.content_type.model, + 'object_id': action.object_id, + 'object_repr': action.object_repr, + 'action_flag': action.get_action_flag_display(), + } + for action in actions + ] + + return Response({'recent_actions': recent_actions_list}) \ No newline at end of file diff --git a/backend/main/settings.py b/backend/main/settings.py index 12a03a0..68f4908 100644 --- a/backend/main/settings.py +++ b/backend/main/settings.py @@ -83,9 +83,10 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', - 'NAME': 'smartchain_db', - 'USER': 'smartchain', - 'PASSWORD': 'venkat*2005', + 'NAME': 'sony', + 'USER': 'sonyin', + 'PASSWORD': 'steamers', + 'HOST': 'localhost', # If running PostgreSQL locally 'PORT': '5432', # Default PostgreSQL port } diff --git a/frontend/app/employee/page.tsx b/frontend/app/employee/page.tsx index b0f9e7b..6cfe9df 100644 --- a/frontend/app/employee/page.tsx +++ b/frontend/app/employee/page.tsx @@ -2,10 +2,10 @@ import React, { useEffect, useState, useMemo, useCallback } from "react"; import { OrdersTable } from "@/components/employee/OrdersTable"; -import { UndeliverableOrders } from "@/components/employee/UndeliverableOrders"; +import { OrderDetails } from "@/components/employee/OrderDetails"; import { DeliveryStatus } from "@/components/employee/DeliveryStatus"; import { CancelOrderDialog } from "@/components/employee/CancelOrderDialog"; -import { DeliveryOrder, UndeliverableOrder } from "@/components/employee/types"; +import { DeliveryOrder } from "@/components/employee/types"; interface PageProps { params: { @@ -13,7 +13,7 @@ interface PageProps { }; } -const API_URL = "http://127.0.0.1:8000/api"; +const API_URL = process.env.REACT_APP_API_URL || "http://127.0.0.1:8000/api"; const getRefreshToken = () => localStorage.getItem("refresh_token"); @@ -53,7 +53,9 @@ export default function EmployeePage({ params }: PageProps) { const [selectedOrderId, setSelectedOrderId] = useState(null); const [selectedReason, setSelectedReason] = useState(""); const [orders, setOrders] = useState([]); - const [undeliverableOrders, setUndeliverableOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [employeeId, setEmployeeId] = useState(null); const getAuthToken = useCallback(async () => { let token = localStorage.getItem("access_token"); @@ -65,7 +67,8 @@ export default function EmployeePage({ params }: PageProps) { const fetchWithAuth = async (url: string, options: RequestInit = {}) => { let token = await getAuthToken(); - if (!token) throw new Error("Authentication token not found. Please log in again."); + if (!token) + throw new Error("Authentication token not found. Please log in again."); const response = await fetch(url, { ...options, @@ -78,7 +81,8 @@ export default function EmployeePage({ params }: PageProps) { if (response.status === 401) { token = await refreshAccessToken(); - if (!token) throw new Error("Authentication token not found. Please log in again."); + if (!token) + throw new Error("Authentication token not found. Please log in again."); return fetch(url, { ...options, @@ -94,34 +98,61 @@ export default function EmployeePage({ params }: PageProps) { }; useEffect(() => { + async function fetchEmployeeId() { + try { + const response = await fetchWithAuth(`${API_URL}/employee_id/`); + if (!response.ok) { + throw new Error("Failed to fetch employee ID"); + } + const data = await response.json(); + setEmployeeId(data.employee_id); + } catch (error) { + setError( + error instanceof Error ? error.message : "Unknown error occurred" + ); + } + } + + fetchEmployeeId(); setMounted(true); }, []); useEffect(() => { async function fetchShipments() { + if (!employeeId) return; + try { - const response = await fetchWithAuth(`${API_URL}/employee_shipments?employeeId=${params.id}`); + setLoading(true); + setError(null); + const response = await fetchWithAuth( + `${API_URL}/employee_shipments?employeeId=${employeeId}` + ); const data = await response.json(); const mappedOrders: DeliveryOrder[] = data.map((shipment: any) => ({ orderId: `SHIP-${shipment.shipment_id}`, orderName: `Order-${shipment.order}`, - phoneNumber: "N/A", // No phone number in API, set default or fetch separately - address: "N/A", // No address in API, set default or fetch separately + phoneNumber: "N/A", + address: "N/A", isDelivered: shipment.status === "delivered", - items: [`Order-${shipment.order}`], // Assuming order details are not available in shipment data + items: [`Order-${shipment.order}`], isCancelled: shipment.status === "cancelled", - cancellationReason: shipment.status === "cancelled" ? "Unknown" : undefined, + cancellationReason: + shipment.status === "cancelled" ? "Unknown" : undefined, })); setOrders(mappedOrders); } catch (error) { - console.error("Failed to fetch shipments:", error); + setError( + error instanceof Error ? error.message : "Unknown error occurred" + ); + } finally { + setLoading(false); } } fetchShipments(); - }, [params.id]); + }, [employeeId]); const handleCancelClick = (orderId: string) => { setSelectedOrderId(orderId); @@ -130,10 +161,14 @@ export default function EmployeePage({ params }: PageProps) { const handleCancelOrder = () => { if (selectedOrderId && selectedReason) { - setOrders(prevOrders => - prevOrders.map(order => + setOrders((prevOrders) => + prevOrders.map((order) => order.orderId === selectedOrderId - ? { ...order, isCancelled: true, cancellationReason: selectedReason } + ? { + ...order, + isCancelled: true, + cancellationReason: selectedReason, + } : order ) ); @@ -145,49 +180,60 @@ export default function EmployeePage({ params }: PageProps) { const handleUpdateStatus = async (shipmentId: number) => { try { - const response = await fetchWithAuth(`${API_URL}/update_shipment_status/`, { - method: "POST", - body: JSON.stringify({ - shipment_id: shipmentId, - status: "delivered", - }), - }); + const response = await fetchWithAuth( + `${API_URL}/update_shipment_status/`, + { + method: "POST", + body: JSON.stringify({ + shipment_id: shipmentId, + status: "delivered", + }), + } + ); if (!response.ok) { throw new Error(`Failed to update status: ${response.statusText}`); } - // Update the local state to reflect the change - setOrders(prevOrders => - prevOrders.map(order => + setOrders((prevOrders) => + prevOrders.map((order) => order.orderId === `SHIP-${shipmentId}` ? { ...order, isDelivered: true } : order ) ); } catch (error) { - console.error("Failed to update shipment status:", error); + setError( + error instanceof Error ? error.message : "Unknown error occurred" + ); } }; const calculatePieChartData = useMemo(() => { - const deliveredCount = orders.filter(order => order.isDelivered && !order.isCancelled).length; - const notDeliveredCount = orders.filter(order => !order.isDelivered && !order.isCancelled).length; - const cancelledCount = orders.filter(order => order.isCancelled).length; - const undeliverableCount = undeliverableOrders.length; + const deliveredCount = orders.filter( + (order) => order.isDelivered && !order.isCancelled + ).length; + const notDeliveredCount = orders.filter( + (order) => !order.isDelivered && !order.isCancelled + ).length; + const cancelledCount = orders.filter((order) => order.isCancelled).length; return [ { name: "Delivered", value: deliveredCount, color: "#22c55e" }, { name: "Pending", value: notDeliveredCount, color: "#3b82f6" }, { name: "Cancelled", value: cancelledCount, color: "#eab308" }, - { name: "Undeliverable", value: undeliverableCount, color: "#94a3b8" } ]; - }, [orders, undeliverableOrders]); + }, [orders]); return (
-

Employee Dashboard - ID: {params.id}

- +

+ Employee Dashboard - ID : {employeeId || "Loading..."} +

+ + {error &&

Error: {error}

} + {loading &&

Loading shipments...

} + - +
- + {mounted && ( )}
); -} \ No newline at end of file +} diff --git a/frontend/app/manufacturer/page.tsx b/frontend/app/manufacturer/page.tsx index 0c09b27..cd45b20 100644 --- a/frontend/app/manufacturer/page.tsx +++ b/frontend/app/manufacturer/page.tsx @@ -36,6 +36,9 @@ import { UsersIcon, } from "lucide-react"; +// API URL for fetching counts +const API_URL = "http://127.0.0.1:8000/api"; + // Interfaces interface OverviewCard { totalOrders: number; @@ -86,6 +89,13 @@ interface ShipmentResponse { results: Shipment[]; } +type Payment = { + id: string; + amount: number; + status: "pending" | "processing" | "success" | "failed"; + email: string; +}; + // Hardcoded data for fallback const testData: OverviewCard = { totalOrders: 0, @@ -128,24 +138,6 @@ const chartConfig = { }, } satisfies ChartConfig; -const notifications: Notification[] = [ - { id: 1, message: "New order placed: #12345", date: "2025-02-15" }, - { id: 2, message: "Low stock alert for Store #24", date: "2025-02-14" }, - { - id: 3, - message: "Delivery agent #12 completed order #67890", - date: "2025-02-14", - }, - { id: 4, message: "New customer feedback received", date: "2025-02-13" }, -]; - -type Payment = { - id: string; - amount: number; - status: "pending" | "processing" | "success" | "failed"; - email: string; -}; - export const payments: Payment[] = [ { id: "728ed52f", @@ -179,21 +171,110 @@ export const payments: Payment[] = [ }, ]; -// API URL for fetching counts -const API_URL = "http://127.0.0.1:8000/api"; - const Dashboard: React.FC = () => { + // State for overview data const [overviewData, setOverviewData] = useState(testData); - const [analytics] = useState(analyticsData); - const [reports] = useState(reportData); - const [notif] = useState(notifications); + const [analytics, setAnalytics] = useState(analyticsData); + const [reports, setReports] = useState(reportData); + + // Loading and error states const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + + // Shipment states const [shipments, setShipments] = useState([]); const [shipmentsLoading, setShipmentsLoading] = useState(true); const [shipmentsError, setShipmentsError] = useState(null); + + // Allocate order states const [allocateLoading, setAllocateLoading] = useState(false); const [allocateError, setAllocateError] = useState(null); + + // Notification states + const [notifications, setNotifications] = useState([]); + const [notificationsLoading, setNotificationsLoading] = + useState(true); + + // Authentication utilities + const getRefreshToken = useCallback(() => { + return localStorage.getItem("refresh_token"); + }, []); + + const refreshAccessToken = useCallback(async (): Promise => { + const refreshToken = getRefreshToken(); + if (!refreshToken) return null; + + try { + const response = await fetch(`${API_URL}/token/refresh/`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ refresh: refreshToken }), + }); + + if (!response.ok) { + console.error("Failed to refresh token"); + localStorage.removeItem("access_token"); + localStorage.removeItem("refresh_token"); + return null; + } + + const data = await response.json(); + if (data.access) { + localStorage.setItem("access_token", data.access); + return data.access; + } + } catch (error) { + console.error("Error refreshing token:", error); + } + + return null; + }, [getRefreshToken]); + + const getAuthToken = useCallback(async (): Promise => { + const token = localStorage.getItem("access_token"); + if (!token) { + return await refreshAccessToken(); + } + return token; + }, [refreshAccessToken]); + + const fetchWithAuth = useCallback( + async (url: string, options: RequestInit = {}) => { + let token = await getAuthToken(); + if (!token) + throw new Error("Authentication token not found. Please log in again."); + + const response = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + + if (response.status === 401) { + token = await refreshAccessToken(); + if (!token) + throw new Error( + "Authentication token not found. Please log in again." + ); + + return fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + } + + return response; + }, + [getAuthToken, refreshAccessToken] + ); + // Define columns for the shipment data table const shipmentColumns = [ { @@ -245,83 +326,11 @@ const Dashboard: React.FC = () => { }, ]; - // Get auth token with error handling - const getAuthToken = useCallback(() => { - const token = localStorage.getItem("access_token"); - if (!token) { - refreshAccessToken(); - } - return token; - }, []); - - const getRefreshToken = () => localStorage.getItem("refresh_token"); - - const refreshAccessToken = async (): Promise => { - const refreshToken = getRefreshToken(); - if (!refreshToken) return null; - - try { - const response = await fetch(`${API_URL}/token/refresh/`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ refresh: refreshToken }), - }); - - if (!response.ok) { - console.error("Failed to refresh token"); - localStorage.removeItem("access_token"); - localStorage.removeItem("refresh_token"); - return null; - } - - const data = await response.json(); - if (data.access) { - localStorage.setItem("access_token", data.access); - return data.access; - } - } catch (error) { - console.error("Error refreshing token:", error); - } - - return null; - }; - const fetchWithAuth = async (url: string, options: RequestInit = {}) => { - let token = await getAuthToken(); - if (!token) throw new Error("Authentication token not found. Please log in again."); - - const response = await fetch(url, { - ...options, - headers: { - ...options.headers, - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - - if (response.status === 401) { - token = await refreshAccessToken(); - if (!token) throw new Error("Authentication token not found. Please log in again."); - - return fetch(url, { - ...options, - headers: { - ...options.headers, - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - } - - return response; - }; - // Fetch shipments data with improved error handling const fetchShipments = useCallback(async () => { try { setShipmentsLoading(true); - const token = getAuthToken(); - - const response = await fetchWithAuth("http://127.0.0.1:8000/api/shipments/"); + const response = await fetchWithAuth(`${API_URL}/shipments/`); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -339,16 +348,54 @@ const Dashboard: React.FC = () => { } finally { setShipmentsLoading(false); } - }, [getAuthToken]); + }, [fetchWithAuth]); + + // Fetch notifications + // Update the fetchNotifications function to use fetchWithAuth + const fetchNotifications = useCallback(async () => { + try { + setNotificationsLoading(true); + const response = await fetchWithAuth(`${API_URL}/recent_actions/`); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.detail || `Server responded with status ${response.status}` + ); + } + + const data = await response.json(); + const formattedData = data.recent_actions.map( + ( + action: { + action_flag: any; + content_type: any; + object_repr: any; + time: string; + }, + index: number + ) => ({ + id: index + 1, + message: `${action.action_flag} on ${action.content_type}: ${action.object_repr}`, + date: action.time.split("T")[0], // Extract only the date + }) + ); + + setNotifications(formattedData); + } catch (error) { + console.error("Error fetching notifications:", error); + } finally { + setNotificationsLoading(false); + } + }, [fetchWithAuth]); // Handle allocate orders with improved error handling and loading state const handleAllocateOrders = async () => { try { setAllocateLoading(true); setAllocateError(null); - const token = getAuthToken(); - console.log(token); - const response = await fetchWithAuth("http://127.0.0.1:8000/api/allocate-orders/", { + + const response = await fetchWithAuth(`${API_URL}/allocate-orders/`, { method: "POST", body: JSON.stringify({}), }); @@ -359,7 +406,8 @@ const Dashboard: React.FC = () => { setAllocateError(errorData.error); // Set the specific error message } else { throw new Error( - errorData.detail || `Server responded with status ${response.status}` + errorData.detail || + `Server responded with status ${response.status}` ); } } else { @@ -370,7 +418,7 @@ const Dashboard: React.FC = () => { } catch (err) { console.error("Error allocating orders:", err); if (!allocateError) { - alert(`Failed to allocate orders: ${(err as Error).message}`); + setAllocateError((err as Error).message); } } finally { setAllocateLoading(false); @@ -381,8 +429,6 @@ const Dashboard: React.FC = () => { const fetchCounts = useCallback(async () => { try { setLoading(true); - const token = getAuthToken(); - const response = await fetchWithAuth(`${API_URL}/count`); if (!response.ok) { @@ -393,12 +439,12 @@ const Dashboard: React.FC = () => { } const countsData: CountsResponse = await response.json(); - setOverviewData((prevData) => ({ - totalOrders: countsData.orders_placed, // Keep existing value since it's not in the API + setOverviewData({ + totalOrders: countsData.orders_placed, numStores: countsData.retailers_available, deliveryAgents: countsData.employees_available, pendingOrders: countsData.pending_orders, - })); + }); setError(null); } catch (err) { @@ -407,24 +453,27 @@ const Dashboard: React.FC = () => { } finally { setLoading(false); } - }, [getAuthToken]); + }, [fetchWithAuth]); - // Set up polling with cleanup + // Set up data fetching and polling useEffect(() => { // Initial fetch fetchCounts(); fetchShipments(); + fetchNotifications(); // Set up polling const countsIntervalId = setInterval(fetchCounts, 5000); const shipmentsIntervalId = setInterval(fetchShipments, 30000); + const notificationsIntervalId = setInterval(fetchNotifications, 10000); // Clean up intervals on unmount return () => { clearInterval(countsIntervalId); clearInterval(shipmentsIntervalId); + clearInterval(notificationsIntervalId); }; - }, [fetchCounts, fetchShipments]); + }, [fetchCounts, fetchShipments, fetchNotifications]); return (
@@ -456,8 +505,6 @@ const Dashboard: React.FC = () => {
)} - - {/* Other Errors */} {error && !error.includes("Authentication") && (
@@ -556,13 +603,12 @@ const Dashboard: React.FC = () => {

- Order Details - + Order + Details

)} - {shipmentsLoading && ( -

Loading transaction data...

- )} + + {shipmentsError && (
@@ -686,15 +731,16 @@ const Dashboard: React.FC = () => {
- {/* Notifications Tab */}

Recent Notifications

- {notif.length > 0 ? ( + {notificationsLoading ? ( +

+ ) : notifications.length > 0 ? (
    - {notif.map((note) => ( + {notifications.map((note) => (
  • {note.message}

    {note.date}

    diff --git a/frontend/components/employee/OrderDetails.tsx b/frontend/components/employee/OrderDetails.tsx new file mode 100644 index 0000000..8a255db --- /dev/null +++ b/frontend/components/employee/OrderDetails.tsx @@ -0,0 +1,152 @@ +import React, { useEffect, useState } from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; + +interface Order { + order_id: number; + required_qty: number; + order_date: string; + status: string; + retailer: number; + product: number; +} + +export const OrderDetails = () => { + const [orders, setOrders] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchOrders = async () => { + try { + let token = localStorage.getItem("access_token"); + if (!token) throw new Error("No authentication token found"); + + let response = await fetchOrdersWithToken(token); + + if (response.status === 401) { + console.log("Access token expired. Trying to refresh..."); + const newToken = await refreshAccessToken(); + + if (newToken) { + localStorage.setItem("access_token", newToken); + response = await fetchOrdersWithToken(newToken); // Retry with new token + } else { + throw new Error("Failed to refresh token"); + } + } + + const data: Order[] = await response.json(); + if (!Array.isArray(data)) throw new Error("Invalid data format from API"); + + setOrders(data); + } catch (error) { + setError( + error instanceof Error ? error.message : "Unknown error occurred" + ); + } finally { + setLoading(false); + } + }; + + const fetchOrdersWithToken = async (token: string) => { + return fetch("http://127.0.0.1:8000/api/employee_orders/", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + }; + + const refreshAccessToken = async (): Promise => { + const refreshToken = localStorage.getItem("refresh_token"); + if (!refreshToken) return null; + + try { + const response = await fetch("http://127.0.0.1:8000/api/token/refresh/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ refresh: refreshToken }), + }); + + if (!response.ok) { + console.error("Failed to refresh token"); + return null; + } + + const data = await response.json(); + return data.access; // Return the new access token + } catch (error) { + console.error("Error refreshing token:", error); + return null; + } + }; + + useEffect(() => { + fetchOrders(); + const interval = setInterval(fetchOrders, 5000); // Poll every 10 seconds + return () => clearInterval(interval); + }, []); + + return ( + + + Orders Details + + +
    + {loading ? ( +

    Loading orders...

    + ) : error ? ( +

    Error: {error}

    + ) : orders.length === 0 ? ( +

    No orders allocated.

    + ) : ( + + + + + + + + + + + + + {orders.map((order) => ( + + + + + + + + + ))} + +
    + Order ID + + Required Quantity + + Order Date + + Retailer ID + + Product ID + + Status +
    {order.order_id}{order.required_qty} + {new Date(order.order_date).toLocaleString()} + {order.retailer}{order.product}{order.status}
    + )} +
    +
    +
    + ); +}; diff --git a/frontend/components/employee/OrdersTable.tsx b/frontend/components/employee/OrdersTable.tsx index e93dd74..17ce3da 100644 --- a/frontend/components/employee/OrdersTable.tsx +++ b/frontend/components/employee/OrdersTable.tsx @@ -62,14 +62,14 @@ export const OrdersTable = ({ orders, onCancelClick, onUpdateStatus }: OrdersTab {!order.isCancelled && !order.isDelivered && ( <> - + */}