Merged all the changes from bhakti's branch into main.

This commit is contained in:
K
2025-04-05 18:24:23 +05:30
107 changed files with 142872 additions and 735 deletions
+78
View File
@@ -0,0 +1,78 @@
import React, { useState } from "react";
const Ai = () => {
const [selectedFile, setSelectedFile] = useState(null);
const [prediction, setPrediction] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const handleFileChange = (e) => {
if (e.target.files && e.target.files[0]) {
setSelectedFile(e.target.files[0]);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!selectedFile) return;
setLoading(true);
setPrediction("");
setError("");
try {
const formData = new FormData();
formData.append("image", selectedFile);
const response = await fetch("http://localhost:3000/predict", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("Prediction request failed");
}
const result = await response.json();
// Assuming the API returns a JSON object with an "output" field.
setPrediction(result.data || "No prediction returned");
} catch (err) {
console.error("Error during prediction:", err);
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div className="max-w-md mx-auto p-6 bg-white shadow rounded-lg">
<h2 className="text-2xl font-bold mb-4 text-center">
Plant disease prediction
</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input
type="file"
accept="image/*"
onChange={handleFileChange}
className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 p-2"
/>
<button
type="submit"
disabled={loading || !selectedFile}
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{loading ? "Predicting..." : "Predict"}
</button>
</form>
{error && <p className="mt-4 text-center text-red-600">{error}</p>}
{prediction && (
<div className="mt-4 p-4 bg-green-100 border border-green-300 rounded">
<h3 className="text-lg font-semibold">Prediction:</h3>
<p>{prediction}</p>
</div>
)}
</div>
);
};
export default Ai;
+1 -1
View File
@@ -12,7 +12,7 @@ const Dashboard = () => {
<AddFarm />
</div>
<div className="mb-4 flex space-x-4">
<div className="mb-4 flex space-x-10">
<Piechart />
<TotalSpent />
</div>
+15 -9
View File
@@ -1,4 +1,5 @@
import React, { useState } from "react";
import { useCreateCropMutation } from "../../../store/api/cropApi";
const AddCrop = ({ farmId }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -12,6 +13,7 @@ const AddCrop = ({ farmId }) => {
const [uploading, setUploading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [createCrop] = useCreateCropMutation();
const handleSubmit = async (e) => {
e.preventDefault();
@@ -28,16 +30,19 @@ const AddCrop = ({ farmId }) => {
if (image) {
formData.append("image", image);
}
console.log(formData);
try {
const response = await fetch(`http://localhost:8000/api/v1/crop`, {
method: "POST",
credentials: "include",
body: formData,
});
if (!response.ok) {
throw new Error("Failed to create crop");
}
const response = await createCrop(formData);
// const response = await fetch(`http://localhost:8000/api/v1/crop`, {
// method: "POST",
// credentials: "include",
// body: formData,
// });
// if (!response.ok) {
// throw new Error("Failed to create crop");
// }
setSuccess("Crop created successfully!");
// Reset form fields
setName("");
@@ -46,6 +51,7 @@ const AddCrop = ({ farmId }) => {
setGrowthStage("");
setHealthStatus("");
setImage(null);
setIsModalOpen(false);
} catch (err) {
setError(err.message);
} finally {
+8 -15
View File
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useCreateFarmMutation } from "../../../store/api/farmApi";
const AddFarm = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -11,6 +12,8 @@ const AddFarm = () => {
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
const navigator = useNavigate();
const [createFarm] = useCreateFarmMutation();
const handleSubmit = async (e) => {
e.preventDefault();
const farmData = {
@@ -21,26 +24,16 @@ const AddFarm = () => {
size: sizeContent,
};
console.log(farmData);
try {
const response = await fetch("http://localhost:8000/api/v1/farm", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(farmData),
});
const res = await createFarm(farmData);
const data = await response.json();
console.log(data);
if (!response.ok) {
throw new Error("Failed to add farm");
if (res.error) {
return null;
}
navigator("farmpage");
setSuccess(true);
setError(null);
setIsModalOpen(false);
@@ -0,0 +1,177 @@
import React, { useState } from "react";
import Loader from "../../../components/Loader";
import { useAddTransactionMutation } from "../../../store/api/financeApi";
const AddTransaction = ({ farmId, financeId }) => {
const [modalOpen, setModalOpen] = useState(false);
const [type, setType] = useState("Expense");
const [amount, setAmount] = useState("");
const [description, setDescription] = useState("");
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState("");
const [addTransaction] = useAddTransactionMutation();
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setMessage("");
const transactionData = {
type,
amount: parseFloat(amount),
description,
};
try {
const response = await addTransaction({ financeId, transactionData });
// const response = await fetch(
// `http://localhost:8000/api/v1/finance/${farmId}/transaction`,
// {
// method: "POST",
// credentials: "include",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({
// type,
// amount: parseFloat(amount),
// description,
// }),
// }
// );
// if (!response.ok) {
// throw new Error("Failed to create transaction");
// }
// const data = await response.json();
setMessage("Transaction created successfully!");
// Optionally clear the form
setType("Expense");
setAmount("");
setDescription("");
} catch (error) {
console.error("Error creating transaction:", error);
setMessage("Error creating transaction.");
} finally {
setLoading(false);
}
};
return (
<>
<button
onClick={() => setModalOpen(true)}
className="block text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
type="button"
>
Add Transaction
</button>
{modalOpen && (
<div className="fixed inset-0 z-50 flex justify-center items-center overflow-y-auto overflow-x-hidden bg-black bg-opacity-50">
<div className="relative p-4 w-full max-w-md">
<div className="relative bg-white rounded-lg shadow dark:bg-gray-700">
{/* Modal Header */}
<div className="flex justify-between items-center p-4 border-b rounded-t dark:border-gray-600">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
Add Transaction
</h3>
<button
onClick={() => setModalOpen(false)}
type="button"
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
></path>
</svg>
</button>
</div>
{/* Modal Body */}
<div className="p-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="transaction-type"
className="block text-sm font-medium text-gray-700"
>
Type
</label>
<select
id="transaction-type"
value={type}
onChange={(e) => setType(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
required
>
<option value="Expense">Expense</option>
<option value="Revenue">Revenue</option>
</select>
</div>
<div>
<label
htmlFor="transaction-amount"
className="block text-sm font-medium text-gray-700"
>
Amount
</label>
<input
type="number"
id="transaction-amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
required
/>
</div>
<div>
<label
htmlFor="transaction-description"
className="block text-sm font-medium text-gray-700"
>
Description
</label>
<textarea
id="transaction-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
required
></textarea>
</div>
<button
type="submit"
disabled={loading}
className="w-full inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{loading ? <Loader></Loader> : "Add Transaction"}
</button>
</form>
{message && (
<p className="mt-4 text-sm text-center text-green-600">
{message}
</p>
)}
</div>
</div>
</div>
</div>
)}
</>
);
};
export default AddTransaction;
@@ -0,0 +1,180 @@
import React, { useState } from "react";
import { useCreateTaskMutation } from "../../../store/api/taskApi";
const CreateTask = ({ farmId, onTaskCreated }) => {
const [farm, setFarm] = useState(farmId);
const [taskType, setTaskType] = useState("Sowing");
const [description, setDescription] = useState("Started Sowing the seeds");
const [assignedDate, setAssignedDate] = useState("2025-04-15");
const [status, setStatus] = useState("Pending");
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState("");
const [modalOpen, setModalOpen] = useState(false);
const [createTask] = useCreateTaskMutation();
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setMessage("");
// In a real app, you might also fetch or choose a crop.
// For now, we assume crop is provided.
const crop = "67ba388f9c4979463e64a39a";
const taskData = {
farm,
crop,
taskType,
description,
assignedDate,
status,
};
try {
const res = await createTask(taskData).unwrap();
setMessage("Task created successfully!");
setModalOpen(false);
// Call the parent's callback with the newly created task
if (onTaskCreated) onTaskCreated(data);
} catch (error) {
console.error("Error creating task:", error);
setMessage("Error creating task.");
} finally {
setLoading(false);
}
};
return (
<>
<button
onClick={() => setModalOpen(true)}
className="block text-white bg-green-600 hover:bg-green-700 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
type="button"
>
Create Task
</button>
{modalOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75">
<div className="relative w-full max-w-2xl p-6 bg-white rounded-lg shadow-xl">
<div className="flex justify-between items-center border-b pb-3">
<h2 className="text-2xl font-bold text-gray-800">Create Task</h2>
<button
onClick={() => setModalOpen(false)}
type="button"
className="text-gray-600 hover:text-gray-800"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<div className="mt-4">
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label
htmlFor="task-type"
className="block text-sm font-medium text-gray-700"
>
Task Type
</label>
<select
id="task-type"
value={taskType}
onChange={(e) => setTaskType(e.target.value)}
className="mt-2 block w-full rounded-md border-gray-300 shadow-sm p-3 focus:ring-2 focus:ring-green-400"
required
>
<option value="Sowing">Sowing</option>
<option value="Watering">Watering</option>
<option value="Fertilization">Fertilization</option>
<option value="Pest Control">Pest Control</option>
<option value="Harvesting">Harvesting</option>
</select>
</div>
<div>
<label
htmlFor="description"
className="block text-sm font-medium text-gray-700"
>
Description
</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
required
></textarea>
</div>
<div>
<label
htmlFor="assignedDate"
className="block text-sm font-medium text-gray-700"
>
Assigned Date
</label>
<input
type="date"
id="assignedDate"
value={assignedDate}
onChange={(e) => setAssignedDate(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
required
/>
</div>
<div>
<label
htmlFor="status"
className="block text-sm font-medium text-gray-700"
>
Status
</label>
<select
id="status"
value={status}
onChange={(e) => setStatus(e.target.value)}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm p-2 focus:ring-2 focus:ring-green-400"
required
>
<option value="Pending">Pending</option>
<option value="Completed">Completed</option>
</select>
</div>
<button
type="submit"
disabled={loading}
className="w-full inline-flex justify-center py-2 px-4 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{loading ? "Creating Task..." : "Create Task"}
</button>
</form>
{message && (
<p className="mt-4 text-center text-sm text-green-600">
{message}
</p>
)}
</div>
</div>
</div>
)}
</>
);
};
export default CreateTask;
@@ -1,29 +1,47 @@
import React, { useState } from "react";
import Laoder from "../../../components/Laoder";
import React, { useEffect, useState } from "react";
import Loader from "../../../components/Loader";
import { useParams } from "react-router-dom";
import { useCreateFinanceMutation } from "../../../store/api/financeApi";
import { useGetFarmByIdQuery } from "../../../store/api/farmApi";
const CreateFinance = () => {
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState("");
// Hardcoded farm ID from your example
const farmId = "67b9e6829c4979463e64a0fc";
const [farm, setFarm] = useState("");
// Hardcoded farm ID from your example
const { farmId } = useParams();
const [createFinance] = useCreateFinanceMutation();
const { data: farmData, isLoading, error } = useGetFarmByIdQuery(farmId);
useEffect(() => {
if (!isLoading && !error && farmData) {
setFarm(farmData);
}
}, [farmData]);
const handleCreateFinance = async () => {
setLoading(true);
setMessage("");
try {
const response = await fetch("http://localhost:8000/api/v1/finance", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ farm: farmId }),
});
const responce = await createFinance({ farm: farmId });
// const response = await fetch("http://localhost:8000/api/v1/finance", {
// method: "POST",
// credentials: "include",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify({ farm: farmId }),
// });
if (!response.ok) {
throw new Error("Failed to create finance");
}
const data = await response.json();
console.log("Finance response:", data);
setMessage("Finance created successfully!");
} catch (error) {
console.error("Error creating finance:", error);
@@ -34,17 +52,13 @@ const CreateFinance = () => {
};
return (
<div className="max-w-md mx-auto p-4 border rounded shadow">
<h2 className="text-xl font-bold mb-4">Create Finance</h2>
<button
onClick={handleCreateFinance}
disabled={loading}
className="mt-4 w-full inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{loading ? <Laoder></Laoder> : "Create Finance"}
</button>
{message && <p className="mt-4 text-sm text-green-600">{message}</p>}
</div>
<button
onClick={handleCreateFinance}
disabled={loading}
className="mt-4 w-30 inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
>
Create Finance
</button>
);
};
@@ -0,0 +1,260 @@
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import Farm from "./Farm";
import CropTable from "./CropTable";
import Transactions from "./Transactions";
import CreateTransactions from "./CreateTransactions";
import Loader from "../../../components/Loader";
import AddTransaction from "./AddTransactions";
import FinanceSummary from "./FinanceSummary";
import CreateTask from "./CreateTask";
import DisplayTast from "./DisplayTask";
import { useGetFarmByIdQuery } from "../../../store/api/farmApi";
import {
useCropHarvestQuery,
useGetCropByIdQuery,
useSuggestFertilizersQuery,
useSuggestNextCropQuery,
useSuggestPesticidesQuery,
} from "../../../store/api/cropApi";
import { PiPottedPlantFill } from "react-icons/pi";
import { GiGrimReaper } from "react-icons/gi";
import { GiProgression } from "react-icons/gi";
function formatDate(isoString) {
const date = new Date(isoString);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
}
export default function CropPage() {
const { cropId } = useParams();
const navigate = useNavigate();
const [farmData, setFarmData] = useState("");
const [loading, setLoading] = useState(true);
const farmId = cropId;
const { data: farm, error, isLoading } = useGetFarmByIdQuery(farmId);
const {
data: crop,
error: cropError,
isLoading: cropLoading,
} = useGetCropByIdQuery(cropId);
useEffect(() => {
if (!isLoading && !error && farm) {
setFarmData(farm);
setLoading(false);
}
}, [farm]);
const {
data: harvest,
isLoading: harvestLoading,
error: harvestError,
} = useCropHarvestQuery(cropId);
const {
data: pesticides,
isLoading: pesticideLoading,
error: pesticideError,
} = useSuggestPesticidesQuery(cropId);
const {
data: fertilizers,
isLoading: fertilizerLoading,
error: fertilizerError,
} = useSuggestFertilizersQuery(cropId);
const {
data: nextCrop,
isLoading: nextCropLoading,
error: nextCropError,
} = useSuggestNextCropQuery(cropId);
return (
<div className="w-full bg-white rounded-lg shadow p-4 space-y-8">
{/* Header Section */}
<header className="mb-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold">{crop?.name}</h2>
<div className="flex items-center space-x-4">
<button
type="button"
className="bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded"
onClick={() => navigate(`/user/dashboard`)}
>
Back
</button>
</div>
</div>
</header>
{/* Crop Table Section */}
<section className="w-full flex justify-center">
<img src={crop?.image} className="w-1/2 h-auto rounded-md" alt="" />
</section>
<section className="w-full flex justify-center flex-col">
<div className="w-full h-auto flex items-center justify-between gap-5">
<div className="w-full">
<button
data-tooltip-target="tooltip-light"
data-tooltip-style="light"
type="button"
class="text-white w-full text-md font-semibold bg-green-700 hover:bg-green-800 focus:ring-4 inline-flex items-center gap-2 focus:outline-none focus:ring-green-300 rounded-lg px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<PiPottedPlantFill className="text-2xl" />{" "}
{formatDate(crop?.plantedDate)}
</button>
<div
id="tooltip-light"
role="tooltip"
class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-xs opacity-0 tooltip"
>
Planted Date
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<div className="w-full">
<button
data-tooltip-target="tooltip-light1"
data-tooltip-style="light"
type="button"
class=" w-full text-black text-md font-semibold bg-green-200 hover:bg-green-300 focus:ring-4 inline-flex items-center gap-2 focus:outline-none focus:ring-green-300 rounded-lg px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<GiGrimReaper className="text-2xl" />{" "}
{formatDate(crop?.harvestDate)}
</button>
<div
id="tooltip-light1"
role="tooltip"
class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-xs opacity-0 tooltip"
>
Harvest Date
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<div className="w-full">
<button
data-tooltip-target="tooltip-light2"
data-tooltip-style="light"
type="button"
class="text-black w-full text-md font-semibold bg-white hover:bg-green-50 focus:ring-4 inline-flex items-center gap-2 focus:outline-none focus:ring-orange-300 border-2 border-green-200 rounded-lg px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
<GiProgression className="text-2xl" />{" "}
{formatDate(crop?.harvestDate)}
</button>
<div
id="tooltip-light2"
role="tooltip"
class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg shadow-xs opacity-0 tooltip"
>
Progress
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</section>
<section className="w-full flex justify-center flex-col">
<h1 className="text-2xl font-bold text-center my-3 mb-10">
Automated Mentoring For Crop Health And Groth
</h1>
<div className="overflow-hidden rounded-xl">
<table>
<thead className="bg-gray-50 ">
<tr>
<th className="px-6 py-3 font-extrabold text-left text-sm text-gray-800 uppercase tracking-wider border-2">
Expected Doubts
</th>
<th className="px-6 py-3 font-extrabold text-left text-sm text-gray-800 uppercase tracking-wider border-2">
Approximate Solution
</th>
</tr>
</thead>
<tbody>
<tr className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap border-2">
Approximate Harvest Date
</td>
<td className="px-6 py-4 whitespace-pre-wrap border-2">
{harvest?.message}
</td>
</tr>
<tr className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap border-2">
Suitable Pesticides For Crop
</td>
<td className="px-6 py-4 whitespace-pre-wrap border-2">
{pesticides?.message}
</td>
</tr>
<tr className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap border-2">
Suitable fertilizers For Crop
</td>
<td className="px-6 py-4 whitespace-pre-wrap border-2">
{fertilizers?.message}
</td>
</tr>
<tr className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap border-2">
Best Crop To Grow
</td>
<td className="px-6 py-4 whitespace-pre-wrap border-2">
{nextCrop?.message}
</td>
</tr>
</tbody>
</table>
</div>
{/* <div className="flex items-start justify-start gap-5">
<h6 className="text-lg font-semibold min-w-[200px] text-nowrap">
Approximate Harvest Date:
</h6>
<p className="w-auto"></p>
</div>
<div className="flex items-start justify-start gap-5">
<h6 className="text-lg font-semibold text-nowrap">
Suitable Pesticides For Crop:
</h6>
<p>{pesticides?.message}</p>
</div>
<div className="flex items-start justify-start gap-5">
<h6 className="text-lg font-semibold text-nowrap">
Suitable fertilizers For Crop:
</h6>
<p>{fertilizers?.message}</p>
</div>
<div className="flex items-start justify-start gap-5">
<h6 className="text-lg font-semibold text-nowrap">
Best Crop To Grow:
</h6>
<p>{nextCrop?.message}</p>
</div> */}
</section>
</div>
);
}
+27 -30
View File
@@ -1,10 +1,22 @@
import React, { useState, useEffect } from "react";
import Laoder from "../../../components/Laoder";
import Loader from "../../../components/Loader";
import { Link, useNavigate } from "react-router-dom";
import { useGetCropsByFarmQuery } from "../../../store/api/cropApi";
const CropTable = ({ farmId }) => {
const [crops, setCrops] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const navigate = useNavigate();
const {
data: cropsData,
error: cropsError,
isLoading: cropsLoading,
} = useGetCropsByFarmQuery(farmId);
const handleRemoveCrop = async (cropId) => {
try {
await fetch(`http://localhost:8000/api/v1/crop/${cropId}`, {
@@ -19,37 +31,16 @@ const CropTable = ({ farmId }) => {
setError(err.message);
}
};
useEffect(() => {
const fetchCrops = async () => {
try {
const response = await fetch(
`http://localhost:8000/api/v1/crop/farm/${farmId}`,
{
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error("Failed to fetch crops");
}
const data = await response.json();
setCrops(data || []);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchCrops();
}, []);
if (cropsData) {
setCrops(cropsData);
setLoading(false);
}
}, [cropsData]);
if (loading) {
return <Laoder></Laoder>;
return <Loader></Loader>;
}
if (error) {
@@ -102,7 +93,13 @@ const CropTable = ({ farmId }) => {
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{crops.map((crop) => (
<tr key={crop._id} className="hover:bg-gray-50">
<tr
key={crop._id}
className="hover:bg-gray-50"
onClick={() => {
navigate(`/user/dashboard/croppage/${crop._id}`);
}}
>
<td className="px-6 py-4 whitespace-nowrap">
<div className="h-12 w-12 rounded-full overflow-hidden">
{crop.image ? (
@@ -0,0 +1,93 @@
import React, { useState, useEffect } from "react";
import Loader from "../../../components/Loader";
import { useGetTasksByFarmQuery } from "../../../store/api/taskApi";
const DisplayTast = ({ farmId }) => {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const {
data: taskList,
error: taskError,
isLoading,
} = useGetTasksByFarmQuery(farmId);
// Function to delete a task and update the state
const handleDeleteTask = async (taskId) => {
try {
const response = await fetch(
`http://localhost:8000/api/v1/task/${taskId}`,
{
method: "DELETE",
credentials: "include",
}
);
// Assuming that a successful deletion returns a 200 status code
if (!response.ok) {
throw new Error("Failed to delete task");
}
// Optionally check the response, but we'll update state regardless.
// Remove the deleted task from state.
setTasks((prevTasks) => prevTasks.filter((task) => task._id !== taskId));
} catch (error) {
console.error("Error deleting task:", error);
// Optionally, you could set an error state here.
}
};
useEffect(() => {
if (taskList) {
setTasks(taskList);
setLoading(false);
}
}, [farmId, taskList]);
if (loading) return <div>Loading tasks...</div>;
if (error) return <div className="text-red-600">Error: {error}</div>;
if (!tasks || tasks.length === 0) {
return <div className="p-4">No tasks found for this farm.</div>;
}
return (
<div className="overflow-x-auto w-full p-4">
<table className="w-full text-sm text-left border">
<thead className="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th className="px-6 py-3">Task Type</th>
<th className="px-6 py-3">Description</th>
<th className="px-6 py-3">Assigned Date</th>
<th className="px-6 py-3">Status</th>
<th className="px-6 py-3">Action</th>
</tr>
</thead>
<tbody>
{tasks.map((task) => (
<tr key={task._id} className="bg-white border-b hover:bg-gray-50">
<td className="px-6 py-4">{task.taskType}</td>
<td className="px-6 py-4">{task.description}</td>
<td className="px-6 py-4">
{new Date(task.assignedDate).toLocaleDateString()}
</td>
<td className="px-6 py-4">{task.status}</td>
<td className="px-6 py-4">
<button
onClick={() => handleDeleteTask(task._id)}
className="block text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5"
type="button"
>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default DisplayTast;
+13 -10
View File
@@ -1,23 +1,26 @@
import React, { useState } from "react";
import { useDeleteFarmMutation } from "../../../store/api/farmApi";
const EditFarm = ({ _id, onDelete }) => {
const [modalOpen, setModalOpen] = useState(false);
const [deleteFarm] = useDeleteFarmMutation();
// This function will run when the "Yes, I'm sure" button is clicked.
const handleDeleteFarm = async () => {
try {
const response = await fetch(`http://localhost:8000/api/v1/farm/${_id}`, {
method: "DELETE",
credentials: "include",
});
const data = await response.json();
console.log("Delete response:", data);
if (data.success) {
// Notify the parent component to update its state
if (onDelete) onDelete(_id);
const res = await deleteFarm(_id);
// const response = await fetch(`http://localhost:8000/api/v1/farm/${_id}`, {
// method: "DELETE",
// credentials: "include",
// });
// const data = await response.json();
if (!res) {
return null;
}
setModalOpen(false); // Close the modal after the operation
window.location.reload();
} catch (error) {
console.error("Error deleting farm:", error);
}
+3 -1
View File
@@ -48,7 +48,9 @@ const Farm = ({ farmData, farmId }) => {
<td className="px-6 py-4">{farmData.soilType}</td>
<td className="px-6 py-4">{farmData.size}</td>
<td className="px-6 py-4">{farmData.waterContent}</td>
<td className="px-6 py-4">Edit</td>
<td className="px-6 py-4">
<EditFarm></EditFarm>
</td>
</tr>
</tbody>
</table>
+69 -49
View File
@@ -4,69 +4,89 @@ import Farm from "./Farm";
import CropTable from "./CropTable";
import Transactions from "./Transactions";
import CreateTransactions from "./CreateTransactions";
import Laoder from "../../../components/Laoder";
import Loader from "../../../components/Loader";
import AddTransaction from "./AddTransactions";
import FinanceSummary from "./FinanceSummary";
import CreateTask from "./CreateTask";
import DisplayTast from "./DisplayTask";
import { useGetFarmByIdQuery } from "../../../store/api/farmApi";
export default function FarmPage() {
const { farmId } = useParams();
const navigate = useNavigate();
const [farmData, setFarmData] = useState(null);
const [farmData, setFarmData] = useState("");
const [loading, setLoading] = useState(true);
const { data: farm, error, isLoading } = useGetFarmByIdQuery(farmId);
useEffect(() => {
async function fetching() {
try {
const response = await fetch(
`http://localhost:8000/api/v1/farm/${farmId}`,
{
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json",
},
}
);
const jsonData = await response.json();
console.log(jsonData);
setFarmData(jsonData);
} catch (error) {
console.error("Error fetching farm data: ", error);
} finally {
setLoading(false);
}
if (!isLoading && !error && farm) {
setFarmData(farm);
setLoading(false);
}
fetching();
}, [farmId]);
}, [farm]);
if (loading) {
return <Laoder></Laoder>;
}
if (!farmData) {
return (
<div className="w-full bg-white rounded-lg shadow p-4">
<p>No farm data found.</p>
</div>
);
}
console.log("My farm id is : ", farmId);
return (
<div className="w-full bg-white rounded-lg shadow p-4">
{/* Back Button */}
<div className="w-full bg-white rounded-lg shadow p-4 space-y-8">
{/* Header Section */}
<header className="mb-4">
<div className="flex justify-end">
<Farm farmData={farmData} farmId={farmId} />
</div>
</header>
<div className="mb-4 flex justify-end">
<Farm farmData={farmData} farmId={farmId}></Farm>
</div>
<div className="mb-4 ">
<CropTable farmId={farmId}></CropTable>
</div>
<div className="mb-4 flex justify-end">
<CreateTransactions farmId={farmId}></CreateTransactions>
</div>
<div className="mb-4 ">
<Transactions farmId={farmId}></Transactions>
</div>
{/* Crop Table Section */}
<section>
<CropTable farmId={farmId} />
</section>
{/* Create Transactions Section */}
<section>
<div className="flex justify-end">
<CreateTransactions farmId={farmId} />
</div>
</section>
{/* Transactions Table Section */}
<section>
<Transactions farmId={farmId} />
</section>
{/* Add Transaction Modal Section */}
<section>
<div className="flex justify-end">
<AddTransaction farmId={farmId} financeId={farmData?.finances?._id} />
</div>
</section>
{/* Finance Summary Section */}
<section>
<div className="flex justify-end">
<FinanceSummary farmId={farmId} financeId={farmData?.finances?._id} />
</div>
</section>
{/* Create Task Section */}
<section>
<div className="flex justify-end">
<CreateTask farmId={farmId} />
</div>
</section>
{/* Display Task Section */}
<section>
<div className="flex justify-end">
<DisplayTast farmId={farmId} />
</div>
</section>
</div>
);
}
@@ -0,0 +1,127 @@
import React, { useState, useEffect } from "react";
import Loader from "../../../components/Loader";
import { useGetTransactionsQuery } from "../../../store/api/financeApi";
function formatTimestamp(isoString) {
const date = new Date(isoString);
return date.toLocaleString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: true, // Ensures AM/PM format
});
}
const FinanceSummary = ({ farmId, financeId }) => {
const [summary, setSummary] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const {
data: transaction,
error: transactionError,
isLoading,
} = useGetTransactionsQuery(financeId);
useEffect(() => {
const fetchSummary = async () => {
setLoading(true);
setError("");
try {
const response = await fetch(
`http://localhost:8000/api/v1/finance/summary/${financeId}`,
{ credentials: "include" }
);
if (!response.ok) {
throw new Error("Failed to fetch summary");
}
const data = await response.json();
setSummary(data);
} catch (err) {
console.error("Error fetching finance summary:", err);
setError("Error fetching summary");
} finally {
setLoading(false);
}
};
fetchSummary();
}, [farmId]);
// if (loading) return <Loader />;
//if (error) return <div className="p-4 text-center text-red-600">{error}</div>;
// Extract only the important fields.
const { totalExpenses, totalRevenue, transactions } = summary || {};
const transactionsCount = Array.isArray(transactions)
? transactions.length
: 0;
return (
<div className="w-full mx-auto p-8 bg-gray-50">
<div className="bg-white rounded-lg shadow overflow-hidden">
<header className="bg-blue-600 px-6 py-4">
<h2 className="text-3xl font-bold text-white">
Transactions Summary
</h2>
</header>
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-6 py-3">
Type of Transaction
</th>
<th scope="col" className="px-6 py-3">
Amount
</th>
<th scope="col" className="px-6 py-3">
Description
</th>
<th scope="col" className="px-6 py-3">
Date
</th>
<th scope="col" className="px-6 py-3">
Action
</th>
</tr>
</thead>
<tbody>
{transaction?.map((transaction) => (
<tr>
<td scope="col" className="px-6 py-3">
{transaction.type}
</td>
<td scope="col" className="px-6 py-3">
{transaction.amount}
</td>
<td scope="col" className="px-6 py-3">
{transaction.description}
</td>
<td scope="col" className="px-6 py-3">
{formatTimestamp(transaction.date)}
</td>
<td className="px-6 py-4">
<button
className="block text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5"
type="button"
>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default FinanceSummary;
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
import Td from "../../../components/Td";
import Laoder from "../../../components/Laoder";
import Loader from "../../../components/Loader";
const Transactions = ({ farmId }) => {
const [data, setData] = useState([]);
@@ -13,7 +13,7 @@ const Transactions = ({ farmId }) => {
.then((response) => response.json())
.then((data) => {
setData(data);
console.log("Fetched data:", data);
setLoading(false);
})
.catch((error) => {
@@ -25,38 +25,28 @@ const Transactions = ({ farmId }) => {
return (
<div className="relative overflow-x-auto shadow-md sm:rounded-lg">
{loading ? (
<Laoder></Laoder>
<Loader />
) : (
<table className="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead className="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" className="px-6 py-3">
Farm name
Total Expenses
</th>
<th scope="col" className="px-6 py-3">
Location
</th>
<th scope="col" className="px-6 py-3">
Type
</th>
<th scope="col" className="px-6 py-3">
Size (acres)
</th>
<th scope="col" className="px-6 py-3">
Action
totalRevenue
</th>
</tr>
</thead>
<tbody>
{Array.isArray(data) && data.length > 0 ? (
data.map((item) => <Td key={item.id} children={item} />)
) : (
<tr>
<td colSpan={5} className="text-center">
No data available
</td>
</tr>
)}
<tr>
<td className="px-6 py-3">
{data.totalExpenses ? data.totalExpenses : "N/A"}
</td>
<td className="px-6 py-3">
{data.totalRevenue ? data.totalRevenue : "N/A"}
</td>
</tr>
</tbody>
</table>
)}
+18 -14
View File
@@ -25,7 +25,7 @@ const MainUserPanel = () => {
const data = await responce.json();
//console.log("User Logged out data is : ", data);
if (data.success === true) {
navigate("/user/login");
@@ -38,7 +38,7 @@ const MainUserPanel = () => {
<div className="container mx-auto p-4">
<div className="flex items-center mb-4 md:hidden">
<img
src={`${user.avatar}`}
src={`/images/default1.png`}
alt="Profile Picture"
className="rounded-full w-10 h-10 mr-2"
/>
@@ -49,7 +49,7 @@ const MainUserPanel = () => {
<div className="w-full md:w-1/4 bg-gradient-to-br from-white/30 to-gray-50/30 rounded-xl shadow-lg p-6 backdrop-blur-lg">
<div className="hidden md:flex items-center mb-6">
<img
src={`${user.avatar}`}
src={`/images/default1.png`}
alt="Profile Picture"
className="w-12 h-12 rounded-full mr-3 border-2 border-green-500"
/>
@@ -69,7 +69,7 @@ const MainUserPanel = () => {
</span>
</Link>
</li>
<li>
{/* <li>
<Link
to={"/user/dashboard/scheduledmeetings"}
className="flex items-center p-2 rounded-md hover:bg-green-100/30 transition-colors backdrop-blur-md"
@@ -79,7 +79,7 @@ const MainUserPanel = () => {
Scheduled Meeting
</span>
</Link>
</li>
</li> */}
<li>
<Link
to={"/user/dashboard/monitoring"}
@@ -91,7 +91,7 @@ const MainUserPanel = () => {
</span>
</Link>
</li>
<li>
{/* <li>
<Link
to={"/user/dashboard/notifications"}
className="flex items-center p-2 rounded-md hover:bg-green-100/30 transition-colors backdrop-blur-md"
@@ -101,8 +101,8 @@ const MainUserPanel = () => {
Notifications
</span>
</Link>
</li>
<li>
</li> */}
{/* <li>
<Link
to={"/user/dashboard/feedback"}
className="flex items-center p-2 rounded-md hover:bg-green-100/30 transition-colors backdrop-blur-md"
@@ -113,7 +113,8 @@ const MainUserPanel = () => {
</span>
</Link>
</li>
<li>
*/}
{/* <li>
<Link
to={"/user/dashboard/support"}
className="flex items-center p-2 rounded-md hover:bg-green-100/30 transition-colors backdrop-blur-md"
@@ -124,6 +125,7 @@ const MainUserPanel = () => {
</span>
</Link>
</li>
*/}
<li>
<Link
to={"/user/dashboard/settings"}
@@ -229,7 +231,7 @@ const MainUserPanel = () => {
</div>
<div className="mb-4">
<img
src={`${user.avatar}`}
src={`/images/default1.png`}
alt="Profile Picture"
className="rounded-full w-24 h-24 mx-auto"
/>
@@ -238,13 +240,15 @@ const MainUserPanel = () => {
Join on {user.createdAt && user.createdAt.substring(0, 10)}
</p>
<p className="text-gray-500 text-sm mt-2">
{user.description == null &&
"I am a Senior Software Engineer at Google and also mentored 50+ students to get their dream job."}
{user.address == null && "Maharashtra, Pune"}
</p>
<div className="flex justify-center mt-4">
<button className="bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-2 px-4 rounded mr-2">
<Link
to="/"
className="bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-2 px-4 rounded mr-2"
>
<FaHome className="text-lg font-extrabold" />
</button>
</Link>
<button className="bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-2 px-4 rounded mr-2">
<IoMdSettings className="text-lg font-extrabold" />
</button>
@@ -20,14 +20,14 @@ const MentorSessionCard = ({ session }) => {
const user = useSelector((store) => store.user);
//console.log("User in the Dashborde : ");
let timeStringToDayName = (dateStr) => {
// for getting day name by time string
// const dateStr = "2024-09-26T04:31:50.646+00:00";
const date = new Date(dateStr);
const dayName = date.toLocaleDateString("en-US", { weekday: "long" });
//console.log(dayName);
return dayName;
};
@@ -48,7 +48,7 @@ const MentorSessionCard = ({ session }) => {
};
const istDate = date.toLocaleString("en-US", options);
console.log(istDate); // Output: "September 26, 2024, 10:01:50 AM"
return istDate;
};
+44 -8
View File
@@ -5,31 +5,67 @@ import PerformanceChart from "../../components/monitoring charts/PerformanceChar
import AlertsPanel from "../../components/monitoring charts/AlertsPanel";
import ActivityFeed from "../../components/monitoring charts/ActivityField";
import Piechart from "../../components/monitoring charts/Piechart";
import { useGetFarmsQuery } from "../../store/api/farmApi";
import PerformanceChart2 from "../../components/monitoring charts/PerformanceChart2";
const calculateSpend = (farms) => {
let totalSpend = 0;
for (let i = 0; i < farms.length; i++) {
if (!farms[i]) continue;
if (!farms[i]?.finances) continue;
if (!farms[i]?.finances?.totalExpenses) continue;
totalSpend += farms[i]?.finances?.totalExpenses;
}
return totalSpend;
};
const calculateRevenue = (farms) => {
let totalSpend = 0;
for (let i = 0; i < farms.length; i++) {
if (!farms[i]) continue;
if (!farms[i]?.finances) continue;
if (!farms[i]?.finances?.totalRevenue) continue;
totalSpend += farms[i]?.finances?.totalRevenue;
}
return totalSpend;
};
const MonitoringPage = () => {
const { data: farms, error, isLoading } = useGetFarmsQuery();
return (
<div className="w-full bg-white rounded-lg shadow p-4">
<div className="p-6">
{/* Summary Metrics */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<MetricsCard title="Active Farms" value="12" />
<MetricsCard title="Total Yield" value="3500 kg" />
<MetricsCard title="Alerts" value="3" />
<MetricsCard title="Active Farms" value={farms?.length} />
<MetricsCard
title="Total Expence"
value={farms && calculateSpend(farms)}
/>
<MetricsCard
title="Total Revenue"
value={farms && calculateRevenue(farms)}
/>
<MetricsCard title="Uptime" value="99.9%" />
</div>
{/* Performance Trend Chart */}
<div className="mb-6 bg-white p-4 rounded-lg shadow">
<h2 className="text-lg font-semibold mb-2">Performance Trend</h2>
<h2 className="text-lg font-semibold mb-2">Expence Trend</h2>
<PerformanceChart />
<h2 className="text-lg font-semibold mb-2 mt-10">Revenue Trend</h2>
<PerformanceChart2 />
</div>
<div className="mb-6 bg-white p-4 rounded-lg shadow">
{/* <div className="mb-6 bg-white p-4 rounded-lg shadow">
<h2 className="text-lg font-semibold mb-2">Performance Trend</h2>
<Piechart></Piechart>
</div>
</div> */}
{/* Alerts and Activity Feed */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="bg-white p-4 rounded-lg shadow">
<h2 className="text-lg font-semibold mb-2">Alerts</h2>
<AlertsPanel />
@@ -38,7 +74,7 @@ const MonitoringPage = () => {
<h2 className="text-lg font-semibold mb-2">Recent Activity</h2>
<ActivityFeed />
</div>
</div>
</div> */}
</div>
</div>
);
+1 -23
View File
@@ -75,29 +75,7 @@ const Notifications = () => {
},
];
// for getting day name by time string
// const dateStr = '2024-09-26T04:31:50.646+00:00';
// const date = new Date(dateStr);
// const dayName = date.toLocaleDateString('en-US', { weekday: 'long' });
// console.log(dayName); // Output: "Thursday"
// for converting the to get time in am or pm
// const utcDateStr = '2024-09-26T04:31:50.646+00:00';
// const date = new Date(utcDateStr);
// India TimeZone is Asia/Kolkata, which is UTC+5:30
// const options = {
// timeZone: 'Asia/Kolkata',
// hour: 'numeric',
// minute: 'numeric',
// second: 'numeric',
// hour12: true,
// year: 'numeric',
// month: 'long',
// day: 'numeric'
// };
// const istDate = date.toLocaleString('en-US', options);
// console.log(istDate); // Output: "September 26, 2024, 10:01:50 AM"
return (
<>
+4 -13
View File
@@ -18,8 +18,6 @@ const Settings = () => {
const loader = useSelector((store) => store.loader);
//console.log("Before the user is : ", user);
const dispatch = useDispatch();
// Optimise the call for the database here you are refreshing the page again and again which makes read and write operation
@@ -27,11 +25,9 @@ const Settings = () => {
event.preventDefault();
formData.append("avatar", avatar);
//console.log("forma daata is : ", formData);
if (avatar) {
dispatch(loaderSliceActions.showLoader());
//console.log("The loader values is : ", loader);
const responce = await fetch(`${BACKEND_URL}/api/v1/user/avatar`, {
method: "PUT",
credentials: "include",
@@ -40,13 +36,11 @@ const Settings = () => {
const finalResponce = await responce.json();
//console.log("Our final responce is : ", finalResponce);
if (finalResponce.success) {
dispatch(loaderSliceActions.hideLoader());
//console.log("The loader values is : ", loader);
dispatch(userSliceActions.addUser(finalResponce.data));
// console.log("Updated User is : ", user);
window.location.reload();
}
}
@@ -68,8 +62,6 @@ const Settings = () => {
const user = await responce.json();
//console.log("User Login Data is here : ", user);
dispatch(userSliceActions.addUser(user.data));
emailElement.current.value = "";
@@ -90,7 +82,7 @@ const Settings = () => {
<div className="w-full h-auto flex items-center justify-center py-7">
<div className="w-[9rem] h-[9rem] overflow-hidden rounded-full object-center">
<img src={`${user.avatar}`} alt="Avatar" />
<img src={`/images/default1.png`} alt="Avatar" />
</div>
</div>
</div>
@@ -144,7 +136,6 @@ const Settings = () => {
className="hidden"
onChange={(e) => {
setAvatar(e.target.files[0]);
//console.log(e.target.files[0]);
}}
/>
</label>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB