820 lines
20 KiB
Vue
820 lines
20 KiB
Vue
<template>
|
|
<div class="overview-container">
|
|
<!-- Form Mode (new=true or edit mode) -->
|
|
<template v-if="isNew || editMode">
|
|
<ClientInformationForm
|
|
ref="clientInfoRef"
|
|
v-model:form-data="formData"
|
|
:is-submitting="isSubmitting"
|
|
:is-edit-mode="editMode"
|
|
@new-client-toggle="handleNewClientToggle"
|
|
@customer-selected="handleCustomerSelected"
|
|
/>
|
|
|
|
<ContactInformationForm
|
|
ref="contactInfoRef"
|
|
v-model:form-data="formData"
|
|
:is-submitting="isSubmitting"
|
|
:is-edit-mode="editMode"
|
|
:is-new-client-locked="isNewClientMode"
|
|
:available-contacts="availableContacts"
|
|
@new-contact-toggle="handleNewContactToggle"
|
|
/>
|
|
|
|
<AddressInformationForm
|
|
v-model:form-data="formData"
|
|
:is-submitting="isSubmitting"
|
|
:is-edit-mode="editMode"
|
|
/>
|
|
</template>
|
|
|
|
<!-- Display Mode (existing client view) -->
|
|
<template v-else>
|
|
<!-- Client Basic Info Card -->
|
|
<div class="info-card">
|
|
<div class="card-header">
|
|
<h3>Client Information</h3>
|
|
<Button
|
|
@click="toggleEditMode"
|
|
icon="pi pi-pencil"
|
|
label="Edit"
|
|
size="small"
|
|
severity="secondary"
|
|
/>
|
|
</div>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<label>Customer Name:</label>
|
|
<span>{{ clientData?.customerName || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Customer Type:</label>
|
|
<span>{{ clientData?.customerType || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Customer Group:</label>
|
|
<span>{{ clientData?.customerGroup || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Territory:</label>
|
|
<span>{{ clientData?.territory || "N/A" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Address Info Card -->
|
|
<div class="info-card" v-if="selectedAddressData">
|
|
<h3>Address Information</h3>
|
|
<div class="info-grid">
|
|
<div class="info-item full-width">
|
|
<label>Address Title:</label>
|
|
<span>{{ selectedAddressData.addressTitle || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item full-width">
|
|
<label>Full Address:</label>
|
|
<span>{{ fullAddress }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>City:</label>
|
|
<span>{{ selectedAddressData.city || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>State:</label>
|
|
<span>{{ selectedAddressData.state || "N/A" }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Zip Code:</label>
|
|
<span>{{ selectedAddressData.pincode || "N/A" }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contact Info Card -->
|
|
<div class="info-card" v-if="selectedAddressData">
|
|
<h3>Contact Information</h3>
|
|
<template v-if="contactsForAddress.length > 0">
|
|
<div v-if="contactsForAddress.length > 1" class="contact-selector">
|
|
<Dropdown
|
|
v-model="selectedContactIndex"
|
|
:options="contactOptions"
|
|
option-label="label"
|
|
option-value="value"
|
|
placeholder="Select Contact"
|
|
class="w-full"
|
|
/>
|
|
</div>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<label>Contact Name:</label>
|
|
<span>{{ contactFullName }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Phone:</label>
|
|
<span>{{ primaryContactPhone }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Email:</label>
|
|
<span>{{ primaryContactEmail }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<p>No contacts available for this address.</p>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Status Cards (only for existing clients) -->
|
|
<div class="status-cards" v-if="!isNew && !editMode && selectedAddressData">
|
|
<div class="status-card">
|
|
<h4>On-Site Meeting</h4>
|
|
<Button
|
|
:label="selectedAddressData.customOnsiteMeetingScheduled || 'Not Started'"
|
|
:severity="getStatusSeverity(selectedAddressData.customOnsiteMeetingScheduled)"
|
|
@click="handleStatusClick('onsite')"
|
|
/>
|
|
</div>
|
|
<div class="status-card">
|
|
<h4>Estimate Sent</h4>
|
|
<Button
|
|
:label="selectedAddressData.customEstimateSentStatus || 'Not Started'"
|
|
:severity="getStatusSeverity(selectedAddressData.customEstimateSentStatus)"
|
|
@click="handleStatusClick('estimate')"
|
|
/>
|
|
</div>
|
|
<div class="status-card">
|
|
<h4>Job Status</h4>
|
|
<Button
|
|
:label="selectedAddressData.customJobStatus || 'Not Started'"
|
|
:severity="getStatusSeverity(selectedAddressData.customJobStatus)"
|
|
@click="handleStatusClick('job')"
|
|
/>
|
|
</div>
|
|
<div class="status-card">
|
|
<h4>Payment Received</h4>
|
|
<Button
|
|
:label="selectedAddressData.customPaymentReceivedStatus || 'Not Started'"
|
|
:severity="getStatusSeverity(selectedAddressData.customPaymentReceivedStatus)"
|
|
@click="handleStatusClick('payment')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="form-actions" v-if="isNew || editMode">
|
|
<Button
|
|
@click="handleCancel"
|
|
:label="editMode ? 'Cancel' : 'Clear'"
|
|
severity="secondary"
|
|
:disabled="isSubmitting"
|
|
/>
|
|
<Button
|
|
@click="handleSave"
|
|
:label="isNew ? 'Create' : 'Update'"
|
|
:loading="isSubmitting"
|
|
:disabled="!isFormValid"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Location Map (only for existing clients) -->
|
|
<div class="map-card" v-if="!isNew && !editMode">
|
|
<h3>Location</h3>
|
|
<LeafletMap
|
|
:latitude="latitude"
|
|
:longitude="longitude"
|
|
:address-title="selectedAddressData?.addressTitle || 'Client Location'"
|
|
map-height="350px"
|
|
:zoom-level="16"
|
|
/>
|
|
<div v-if="latitude && longitude" class="coordinates-info">
|
|
<small>
|
|
<strong>Coordinates:</strong>
|
|
{{ parseFloat(latitude).toFixed(6) }}, {{ parseFloat(longitude).toFixed(6) }}
|
|
</small>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Confirmation Dialog -->
|
|
<Dialog
|
|
v-model:visible="showEditConfirmDialog"
|
|
header="Confirm Edit"
|
|
:modal="true"
|
|
:closable="false"
|
|
class="confirm-dialog"
|
|
>
|
|
<p>
|
|
Are you sure you want to edit this client information? This will enable editing
|
|
mode.
|
|
</p>
|
|
<template #footer>
|
|
<Button
|
|
label="Cancel"
|
|
severity="secondary"
|
|
@click="showEditConfirmDialog = false"
|
|
/>
|
|
<Button label="Yes, Edit" @click="confirmEdit" />
|
|
</template>
|
|
</Dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch, onMounted } from "vue";
|
|
import Badge from "primevue/badge";
|
|
import Button from "primevue/button";
|
|
import Dialog from "primevue/dialog";
|
|
import Dropdown from "primevue/dropdown";
|
|
import LeafletMap from "../common/LeafletMap.vue";
|
|
import ClientInformationForm from "./ClientInformationForm.vue";
|
|
import ContactInformationForm from "./ContactInformationForm.vue";
|
|
import AddressInformationForm from "./AddressInformationForm.vue";
|
|
import DataUtils from "../../utils";
|
|
import Api from "../../api";
|
|
import { useRouter } from "vue-router";
|
|
import { useNotificationStore } from "../../stores/notifications-primevue";
|
|
|
|
const props = defineProps({
|
|
clientData: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
selectedAddress: {
|
|
type: String,
|
|
default: "",
|
|
},
|
|
isNew: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const router = useRouter();
|
|
const notificationStore = useNotificationStore();
|
|
|
|
// Refs for child components
|
|
const clientInfoRef = ref(null);
|
|
const contactInfoRef = ref(null);
|
|
|
|
// Form state
|
|
const editMode = ref(false);
|
|
const showEditConfirmDialog = ref(false);
|
|
const isSubmitting = ref(false);
|
|
const isNewClientMode = ref(false);
|
|
const availableContacts = ref([]);
|
|
|
|
// Form data
|
|
const formData = ref({
|
|
customerName: "",
|
|
customerType: "",
|
|
addressTitle: "",
|
|
addressLine1: "",
|
|
addressLine2: "",
|
|
pincode: "",
|
|
city: "",
|
|
state: "",
|
|
contacts: [],
|
|
});
|
|
|
|
// Initialize form data when component mounts
|
|
onMounted(() => {
|
|
if (props.isNew) {
|
|
resetForm();
|
|
isNewClientMode.value = true; // Set to true for new client mode
|
|
console.log("Mounted in new client mode - initialized empty form");
|
|
} else if (props.clientData && Object.keys(props.clientData).length > 0) {
|
|
populateFormFromClientData();
|
|
console.log("Mounted with existing client data - populated form");
|
|
} else {
|
|
resetForm();
|
|
console.log("Mounted with no client data - initialized empty form");
|
|
}
|
|
});
|
|
|
|
// Watch for clientData changes
|
|
watch(
|
|
() => props.clientData,
|
|
(newData) => {
|
|
if (props.isNew) {
|
|
resetForm();
|
|
} else if (newData && Object.keys(newData).length > 0) {
|
|
populateFormFromClientData();
|
|
} else {
|
|
resetForm();
|
|
}
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
// Watch for isNew prop changes
|
|
watch(
|
|
() => props.isNew,
|
|
(isNewValue) => {
|
|
if (isNewValue) {
|
|
resetForm();
|
|
editMode.value = false;
|
|
console.log("Switched to new client mode - reset form data");
|
|
} else if (props.clientData && Object.keys(props.clientData).length > 0) {
|
|
populateFormFromClientData();
|
|
} else {
|
|
resetForm();
|
|
}
|
|
},
|
|
{ immediate: false },
|
|
);
|
|
|
|
// Find the address data object that matches the selected address string
|
|
const selectedAddressData = computed(() => {
|
|
if (!props.clientData?.addresses || !props.selectedAddress) {
|
|
return null;
|
|
}
|
|
|
|
return props.clientData.addresses.find(
|
|
(addr) => DataUtils.calculateFullAddress(addr) === props.selectedAddress,
|
|
);
|
|
});
|
|
|
|
// Get coordinates from the selected address
|
|
const latitude = computed(() => {
|
|
if (!selectedAddressData.value) return null;
|
|
return selectedAddressData.value.customLatitude || selectedAddressData.value.latitude || null;
|
|
});
|
|
|
|
const longitude = computed(() => {
|
|
if (!selectedAddressData.value) return null;
|
|
return (
|
|
selectedAddressData.value.customLongitude || selectedAddressData.value.longitude || null
|
|
);
|
|
});
|
|
|
|
// Calculate full address for display
|
|
const fullAddress = computed(() => {
|
|
if (!selectedAddressData.value) return "N/A";
|
|
return DataUtils.calculateFullAddress(selectedAddressData.value);
|
|
});
|
|
|
|
// Get contacts linked to the selected address
|
|
const contactsForAddress = computed(() => {
|
|
if (!selectedAddressData.value?.customLinkedContacts || !props.clientData?.contacts) return [];
|
|
return selectedAddressData.value.customLinkedContacts
|
|
.map((link) => props.clientData.contacts.find((c) => c.name === link.contact))
|
|
.filter(Boolean);
|
|
});
|
|
|
|
// Selected contact index for display
|
|
const selectedContactIndex = ref(0);
|
|
|
|
// Options for contact dropdown
|
|
const contactOptions = computed(() =>
|
|
contactsForAddress.value.map((c, i) => ({
|
|
label:
|
|
c.fullName || `${c.firstName || ""} ${c.lastName || ""}`.trim() || "Unnamed Contact",
|
|
value: i,
|
|
})),
|
|
);
|
|
|
|
// Selected contact for display
|
|
const selectedContact = computed(
|
|
() => contactsForAddress.value[selectedContactIndex.value] || null,
|
|
);
|
|
|
|
// Calculate contact full name
|
|
const contactFullName = computed(() => {
|
|
if (!selectedContact.value) return "N/A";
|
|
return (
|
|
selectedContact.value.fullName ||
|
|
`${selectedContact.value.firstName || ""} ${selectedContact.value.lastName || ""}`.trim() ||
|
|
"N/A"
|
|
);
|
|
});
|
|
|
|
// Calculate primary contact phone
|
|
const primaryContactPhone = computed(() => {
|
|
if (!selectedContact.value) return "N/A";
|
|
return selectedContact.value.phone || selectedContact.value.mobileNo || "N/A";
|
|
});
|
|
|
|
// Calculate primary contact email
|
|
const primaryContactEmail = computed(() => {
|
|
if (!selectedContact.value) return "N/A";
|
|
return selectedContact.value.emailId || selectedContact.value.customEmail || "N/A";
|
|
});
|
|
|
|
// Form validation
|
|
const isFormValid = computed(() => {
|
|
const hasCustomerName = formData.value.customerName?.trim();
|
|
const hasCustomerType = formData.value.customerType?.trim();
|
|
const hasAddressTitle = formData.value.addressTitle?.trim();
|
|
const hasAddressLine1 = formData.value.addressLine1?.trim();
|
|
const hasPincode = formData.value.pincode?.trim();
|
|
const hasCity = formData.value.city?.trim();
|
|
const hasState = formData.value.state?.trim();
|
|
const hasContacts = formData.value.contacts && formData.value.contacts.length > 0;
|
|
const primaryContact = formData.value.contacts?.find((c) => c.isPrimary);
|
|
const hasFirstName = primaryContact?.firstName?.trim();
|
|
const hasLastName = primaryContact?.lastName?.trim();
|
|
|
|
return (
|
|
hasCustomerName &&
|
|
hasCustomerType &&
|
|
hasAddressTitle &&
|
|
hasAddressLine1 &&
|
|
hasPincode &&
|
|
hasCity &&
|
|
hasState &&
|
|
hasContacts &&
|
|
hasFirstName &&
|
|
hasLastName
|
|
);
|
|
});
|
|
|
|
// Helper function to get badge severity based on status
|
|
const getStatusSeverity = (status) => {
|
|
switch (status) {
|
|
case "Not Started":
|
|
return "danger";
|
|
case "In Progress":
|
|
return "warn";
|
|
case "Completed":
|
|
return "success";
|
|
default:
|
|
return "secondary";
|
|
}
|
|
};
|
|
|
|
// Handle status button clicks
|
|
const handleStatusClick = (type) => {
|
|
let status;
|
|
let path;
|
|
|
|
switch (type) {
|
|
case "onsite":
|
|
status = selectedAddressData.value.customOnsiteMeetingScheduled || "Not Started";
|
|
path = "/schedule-onsite";
|
|
break;
|
|
case "estimate":
|
|
status = selectedAddressData.value.customEstimateSentStatus || "Not Started";
|
|
path = "/estimate";
|
|
break;
|
|
case "job":
|
|
status = selectedAddressData.value.customJobStatus || "Not Started";
|
|
path = "/job";
|
|
break;
|
|
case "payment":
|
|
status = selectedAddressData.value.customPaymentReceivedStatus || "Not Started";
|
|
path = "/invoices";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
const query = { address: fullAddress.value };
|
|
if (status === "Not Started") {
|
|
query.new = true;
|
|
}
|
|
|
|
router.push({ path, query });
|
|
};
|
|
|
|
// Form methods
|
|
const resetForm = () => {
|
|
formData.value = {
|
|
customerName: "",
|
|
customerType: "",
|
|
addressTitle: "",
|
|
addressLine1: "",
|
|
addressLine2: "",
|
|
pincode: "",
|
|
city: "",
|
|
state: "",
|
|
contacts: [],
|
|
};
|
|
availableContacts.value = [];
|
|
isNewClientMode.value = false;
|
|
editMode.value = false;
|
|
console.log("Form reset - all fields cleared");
|
|
};
|
|
|
|
const populateFormFromClientData = () => {
|
|
if (!selectedAddressData.value) return;
|
|
|
|
formData.value = {
|
|
customerName: props.clientData.customerName || "",
|
|
customerType: props.clientData.customerType || "",
|
|
addressTitle: selectedAddressData.value.addressTitle || "",
|
|
addressLine1: selectedAddressData.value.addressLine1 || "",
|
|
addressLine2: selectedAddressData.value.addressLine2 || "",
|
|
pincode: selectedAddressData.value.pincode || "",
|
|
city: selectedAddressData.value.city || "",
|
|
state: selectedAddressData.value.state || "",
|
|
contacts:
|
|
contactsForAddress.value.map((c) => ({
|
|
firstName: c.firstName || "",
|
|
lastName: c.lastName || "",
|
|
phoneNumber: c.phone || c.mobileNo || "",
|
|
email: c.emailId || c.customEmail || "",
|
|
contactRole: c.role || "",
|
|
isPrimary: c.isPrimaryContact || false,
|
|
})) || [],
|
|
};
|
|
|
|
// Populate available contacts if any
|
|
if (contactsForAddress.value.length > 0) {
|
|
availableContacts.value = contactsForAddress.value;
|
|
}
|
|
};
|
|
|
|
// Event handlers
|
|
const handleNewClientToggle = (isNewClient) => {
|
|
isNewClientMode.value = isNewClient;
|
|
if (isNewClient) {
|
|
// Reset form when toggling to new client
|
|
resetForm();
|
|
}
|
|
};
|
|
|
|
const handleCustomerSelected = (clientData) => {
|
|
// When a customer is selected, populate available contacts from the first address
|
|
if (clientData.addresses && clientData.addresses.length > 0) {
|
|
availableContacts.value = clientData.addresses[0].contacts || [];
|
|
} else {
|
|
availableContacts.value = [];
|
|
}
|
|
};
|
|
|
|
const handleNewContactToggle = (isNewContact) => {
|
|
if (!isNewContact && availableContacts.value.length === 0) {
|
|
notificationStore.addWarning("No contacts available for this customer.");
|
|
}
|
|
};
|
|
|
|
// Edit mode methods
|
|
const toggleEditMode = () => {
|
|
showEditConfirmDialog.value = true;
|
|
};
|
|
|
|
const confirmEdit = () => {
|
|
showEditConfirmDialog.value = false;
|
|
editMode.value = true;
|
|
populateFormFromClientData();
|
|
};
|
|
|
|
// Save/Cancel actions
|
|
const handleSave = async () => {
|
|
if (!isFormValid.value) {
|
|
notificationStore.addError("Please fill in all required fields");
|
|
return;
|
|
}
|
|
|
|
isSubmitting.value = true;
|
|
|
|
try {
|
|
// Prepare client data for upsert
|
|
const clientData = {
|
|
customerName: formData.value.customerName,
|
|
customerType: formData.value.customerType,
|
|
addressTitle: formData.value.addressTitle,
|
|
addressLine1: formData.value.addressLine1,
|
|
addressLine2: formData.value.addressLine2,
|
|
pincode: formData.value.pincode,
|
|
city: formData.value.city,
|
|
state: formData.value.state,
|
|
contacts: formData.value.contacts,
|
|
};
|
|
|
|
console.log("Upserting client with data:", clientData);
|
|
|
|
// Call the upsert API
|
|
const result = await Api.createClient(clientData);
|
|
|
|
// Calculate full address for redirect
|
|
const fullAddressParts = [formData.value.addressLine1];
|
|
if (formData.value.addressLine2?.trim()) {
|
|
fullAddressParts.push(formData.value.addressLine2);
|
|
}
|
|
fullAddressParts.push(`${formData.value.city}, ${formData.value.state}`);
|
|
fullAddressParts.push(formData.value.pincode);
|
|
const fullAddress = fullAddressParts.join(" ");
|
|
|
|
if (props.isNew) {
|
|
notificationStore.addSuccess(
|
|
`Client ${formData.value.customerName} created successfully!`,
|
|
);
|
|
|
|
// Redirect to the new client page
|
|
await router.push({
|
|
path: "/client",
|
|
query: {
|
|
client: formData.value.customerName,
|
|
address: fullAddress,
|
|
},
|
|
});
|
|
} else {
|
|
notificationStore.addSuccess("Client updated successfully!");
|
|
editMode.value = false;
|
|
|
|
// Reload the client data
|
|
// Note: Parent component should handle reloading
|
|
}
|
|
} catch (error) {
|
|
console.error("Error saving client:", error);
|
|
notificationStore.addError("Failed to save client information");
|
|
} finally {
|
|
isSubmitting.value = false;
|
|
}
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
if (props.isNew) {
|
|
// Clear form for new client
|
|
resetForm();
|
|
} else {
|
|
// Exit edit mode and restore original data
|
|
editMode.value = false;
|
|
populateFormFromClientData();
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.overview-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.info-card,
|
|
.map-card {
|
|
background: var(--surface-card);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
border: 1px solid var(--surface-border);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.card-header h3 {
|
|
margin: 0;
|
|
}
|
|
|
|
.info-card h3,
|
|
.map-card h3 {
|
|
margin: 0 0 1rem 0;
|
|
color: var(--text-color);
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.info-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.info-item.full-width {
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.info-item label {
|
|
font-weight: 500;
|
|
color: var(--text-color-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.info-item span {
|
|
color: var(--text-color);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
.contact-selector {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* Form input styling */
|
|
.info-item :deep(.p-inputtext),
|
|
.info-item :deep(.p-autocomplete),
|
|
.info-item :deep(.p-select) {
|
|
width: 100%;
|
|
}
|
|
|
|
.info-item :deep(.p-autocomplete .p-inputtext) {
|
|
width: 100%;
|
|
}
|
|
|
|
/* Required field indicator */
|
|
.info-item label:has(+ .p-inputtext[required])::after,
|
|
.info-item label:has(+ .p-autocomplete)::after {
|
|
content: " *";
|
|
color: var(--red-500);
|
|
}
|
|
|
|
.status-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.status-card {
|
|
background: var(--surface-card);
|
|
border-radius: 8px;
|
|
padding: 1rem;
|
|
border: 1px solid var(--surface-border);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.status-card h4 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 1rem;
|
|
padding: 1.5rem;
|
|
background: var(--surface-card);
|
|
border-radius: 8px;
|
|
border: 1px solid var(--surface-border);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.coordinates-info {
|
|
margin-top: 0.75rem;
|
|
text-align: center;
|
|
color: var(--text-color-secondary);
|
|
padding-top: 0.75rem;
|
|
border-top: 1px solid var(--surface-border);
|
|
}
|
|
|
|
.confirm-dialog {
|
|
max-width: 400px;
|
|
}
|
|
|
|
.confirm-dialog :deep(.p-dialog-footer) {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
/* Utilities */
|
|
.w-full {
|
|
width: 100% !important;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.overview-container {
|
|
padding: 0.5rem;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.info-card,
|
|
.map-card {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.info-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.status-cards {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.form-actions {
|
|
padding: 1rem;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.card-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 0.5rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.status-cards {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|