305 lines
8.3 KiB
Vue
305 lines
8.3 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed } from "vue";
|
|
import axios from "axios";
|
|
import { usePageTitle } from "../composables/Core/usePageTitle";
|
|
import { useNavigate } from "../composables/Core/useNavigate";
|
|
import { useModal } from "../composables/Core/useModal";
|
|
|
|
usePageTitle("Enroll Farmer");
|
|
const { navigate } = useNavigate();
|
|
const modal = useModal();
|
|
|
|
const props = defineProps({
|
|
target: String,
|
|
});
|
|
|
|
// Cooperative
|
|
const cooperative = ref(null);
|
|
const loadingCoop = ref(true);
|
|
|
|
// Search
|
|
const searchQuery = ref("");
|
|
const searchResults = ref([]);
|
|
const isSearching = ref(false);
|
|
|
|
// Selected User
|
|
const selectedUser = ref(null);
|
|
|
|
// Form
|
|
const form = ref({
|
|
farm_name: "",
|
|
farm_location: "",
|
|
main_crops: [],
|
|
});
|
|
const cropInput = ref("");
|
|
const isSaving = ref(false);
|
|
|
|
const fetchCooperative = async () => {
|
|
if (!props.target) return;
|
|
loadingCoop.value = true;
|
|
try {
|
|
const response = await axios.post("/Cooperatives/Get", {
|
|
hashkey: props.target,
|
|
});
|
|
if (response.data.success) {
|
|
cooperative.value = response.data.data;
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch cooperative", error);
|
|
} finally {
|
|
loadingCoop.value = false;
|
|
}
|
|
};
|
|
|
|
const searchUsers = async () => {
|
|
if (searchQuery.value.length < 2) {
|
|
searchResults.value = [];
|
|
return;
|
|
}
|
|
isSearching.value = true;
|
|
try {
|
|
const response = await axios.post("/Farmers/List", {
|
|
search: searchQuery.value,
|
|
});
|
|
if (response.data.success) {
|
|
searchResults.value = response.data.data;
|
|
}
|
|
} catch (error) {
|
|
console.error("Search failed", error);
|
|
} finally {
|
|
isSearching.value = false;
|
|
}
|
|
};
|
|
|
|
const selectUser = (user) => {
|
|
selectedUser.value = user;
|
|
searchResults.value = [];
|
|
searchQuery.value = "";
|
|
};
|
|
|
|
const clearSelection = () => {
|
|
selectedUser.value = null;
|
|
};
|
|
|
|
const addCrop = () => {
|
|
if (
|
|
cropInput.value &&
|
|
!form.value.main_crops.includes(cropInput.value)
|
|
) {
|
|
form.value.main_crops.push(cropInput.value);
|
|
cropInput.value = "";
|
|
}
|
|
};
|
|
|
|
const removeCrop = (crop) => {
|
|
form.value.main_crops = form.value.main_crops.filter((c) => c !== crop);
|
|
};
|
|
|
|
const enrollFarmer = async () => {
|
|
if (!selectedUser.value) {
|
|
modal.open({ title: "Error", body: "Please select a user first." });
|
|
return;
|
|
}
|
|
|
|
isSaving.value = true;
|
|
try {
|
|
const response = await axios.post("/Farmers/Register", {
|
|
...form.value,
|
|
organization_hash: props.target,
|
|
});
|
|
|
|
if (response.data.success) {
|
|
modal.open({
|
|
title: "Success",
|
|
body: "Farmer enrolled successfully!",
|
|
onClose: () =>
|
|
navigate({ page: "CooperativeDetail", target: props.target }),
|
|
});
|
|
} else {
|
|
modal.open({
|
|
title: "Error",
|
|
body: response.data.message || "Enrollment failed. Please try again.",
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error("Enrollment failed", error);
|
|
modal.open({
|
|
title: "Error",
|
|
body:
|
|
error.response?.data?.message ||
|
|
"Failed to enroll farmer. Please try again.",
|
|
});
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(fetchCooperative);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="enroll-farmer pb-5">
|
|
<div class="tf-container mt-4">
|
|
<div class="mb-4">
|
|
<button
|
|
@click="navigate({ page: 'CooperativeDetail', target: target })"
|
|
class="btn btn-link text-decoration-none p-0 mb-2 text-primary"
|
|
>
|
|
<i class="fas fa-arrow-left me-1"></i> Back to Cooperative
|
|
</button>
|
|
<h3 class="fw_6 mb-0">Enroll Farmer</h3>
|
|
<p v-if="cooperative" class="text-muted">
|
|
Adding farmer to <strong>{{ cooperative.name }}</strong>
|
|
</p>
|
|
</div>
|
|
|
|
<div v-if="loadingCoop" class="text-center py-5">
|
|
<i class="fas fa-spinner fa-spin fa-2x text-primary"></i>
|
|
</div>
|
|
|
|
<div v-else>
|
|
<!-- User Selection -->
|
|
<div class="card border-0 shadow-sm rounded-20 p-4 mb-4">
|
|
<h5 class="fw_6 mb-3">1. Select User</h5>
|
|
|
|
<div v-if="!selectedUser">
|
|
<div class="position-relative">
|
|
<input
|
|
v-model="searchQuery"
|
|
@input="searchUsers"
|
|
type="text"
|
|
class="form-control rounded-pill"
|
|
placeholder="Search user by name or mobile..."
|
|
/>
|
|
<div
|
|
v-if="searchResults.length > 0"
|
|
class="search-results card border-0 shadow-sm mt-2 position-absolute w-100 z-1"
|
|
>
|
|
<div
|
|
v-for="user in searchResults"
|
|
:key="user.hashkey"
|
|
@click="selectUser(user)"
|
|
class="p-3 border-bottom cursor-pointer hover-bg"
|
|
>
|
|
<div class="fw-bold">
|
|
{{ user.fullname || user.firstname + " " + user.lastname }}
|
|
</div>
|
|
<small class="text-muted">{{ user.mobile }}</small>
|
|
</div>
|
|
</div>
|
|
<div v-if="isSearching" class="text-center mt-2">
|
|
<small class="text-muted"
|
|
><i class="fas fa-spinner fa-spin me-1"></i> Searching...</small
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="d-flex align-items-center justify-content-between bg-light p-3 rounded-15">
|
|
<div>
|
|
<div class="fw-bold">
|
|
{{
|
|
selectedUser.fullname ||
|
|
selectedUser.firstname + " " + selectedUser.lastname
|
|
}}
|
|
</div>
|
|
<small class="text-muted">{{ selectedUser.mobile }}</small>
|
|
</div>
|
|
<button @click="clearSelection" class="btn btn-sm btn-outline-danger rounded-pill">
|
|
Change
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Farmer Details -->
|
|
<div class="card border-0 shadow-sm rounded-20 p-4">
|
|
<h5 class="fw_6 mb-3">2. Farm Details</h5>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">Farm Name</label>
|
|
<input
|
|
v-model="form.farm_name"
|
|
type="text"
|
|
class="form-control rounded-pill"
|
|
placeholder="Enter farm name"
|
|
/>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">Farm Location</label>
|
|
<textarea
|
|
v-model="form.farm_location"
|
|
class="form-control rounded-15"
|
|
rows="2"
|
|
placeholder="Barangay, Municipality, Province"
|
|
></textarea>
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label class="form-label small fw-bold">Main Crops</label>
|
|
<div class="d-flex gap-2 mb-2">
|
|
<input
|
|
v-model="cropInput"
|
|
@keyup.enter="addCrop"
|
|
type="text"
|
|
class="form-control rounded-pill"
|
|
placeholder="e.g. Rice, Corn"
|
|
/>
|
|
<button
|
|
@click="addCrop"
|
|
type="button"
|
|
class="btn btn-primary rounded-pill px-4"
|
|
>
|
|
Add
|
|
</button>
|
|
</div>
|
|
<div class="d-flex flex-wrap gap-2">
|
|
<span
|
|
v-for="crop in form.main_crops"
|
|
:key="crop"
|
|
class="badge bg-light text-dark rounded-pill border px-3 py-2"
|
|
>
|
|
{{ crop }}
|
|
<i
|
|
@click="removeCrop(crop)"
|
|
class="fas fa-times ms-2 cursor-pointer text-danger"
|
|
></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
:disabled="isSaving || !selectedUser"
|
|
@click="enrollFarmer"
|
|
class="btn btn-primary w-100 rounded-pill py-3 fw-bold"
|
|
>
|
|
<span v-if="isSaving"
|
|
><i class="fas fa-spinner fa-spin me-2"></i> Enrolling...</span
|
|
>
|
|
<span v-else>Enroll Farmer</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.rounded-20 {
|
|
border-radius: 20px;
|
|
}
|
|
.rounded-15 {
|
|
border-radius: 15px;
|
|
}
|
|
.cursor-pointer {
|
|
cursor: pointer;
|
|
}
|
|
.hover-bg:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
.search-results {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
}
|
|
</style>
|