Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appointment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
__package_name__ = "django-appointment"
__url__ = "https://github.com/adamspd/django-appointment"
__package_website__ = "https://django-appt.adamspierredavid.com/"
__version__ = "3.4.0"
__version__ = "3.4.1"
__test_version__ = False
84 changes: 78 additions & 6 deletions appointment/static/js/app_admin/staff_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,46 @@ document.addEventListener("DOMContentLoaded", function () {
});
});

const AppStateProxy = new Proxy(AppState, {
set(target, property, value) {
console.log(`Setting ${property} to ${value}`)
// Check if the property being changed is 'isCreating'
if (value === true) {
attachEventListeners(); // Attach event listeners if isCreating becomes true
// (didn't check if property is isCreating, since AppStateProxy is only set with it)
}
target[property] = value; // Set the property value
return true; // Indicate successful setting
}
});

function attachEventListeners() {
// Checks if the DOM is already loaded
if (document.readyState === "complete" || document.readyState === "interactive") {
// DOM is already ready, attach event listeners directly
attachEventListenersToDropdown();
} else {
// If the DOM is not yet ready, then wait for the DOMContentLoaded event
document.addEventListener('DOMContentLoaded', function () {
attachEventListenersToDropdown();
});
}
}

function attachEventListenersToDropdown() {
const staffDropdown = document.getElementById('staffSelect');
if (staffDropdown && !staffDropdown.getAttribute('listener-attached')) {
staffDropdown.setAttribute('listener-attached', 'true');
staffDropdown.addEventListener('change', async function () {
const selectedStaffId = this.value;
const servicesDropdown = document.getElementById('serviceSelect');
const services = await fetchServicesForStaffMember(selectedStaffId);
updateServicesDropdown(servicesDropdown, services);
});
}
}


function initializeCalendar() {
const formattedAppointments = formatAppointmentsForCalendar(appointments);
const calendarEl = document.getElementById('calendar');
Expand Down Expand Up @@ -285,7 +325,7 @@ function closeModal() {
cancelButton.style.display = "none";

// Reset the editing flag
AppState.isEditingAppointment = false;
AppStateProxy.isEditingAppointment = false;

// Close the modal
$('#eventDetailsModal').modal('hide');
Expand Down Expand Up @@ -419,6 +459,32 @@ async function populateStaffMembers(selectedStaffId, isEditMode = false) {
return selectElement;
}

// Function to fetch services for a specific staff member
async function fetchServicesForStaffMember(staffId) {
const url = `${fetchServiceListForStaffURL}?staff_id=${staffId}`;
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
return data.services_offered || [];
} catch (error) {
console.error("Error fetching services: ", error);
return []; // Return an empty array in case of error
}
}

// Function to update services dropdown options
function updateServicesDropdown(dropdown, services) {
// Clear existing options
dropdown.innerHTML = '';

// Populate with new options
services.forEach(service => {
const option = new Option(service.name, service.id); // Assuming service object has id and name properties
dropdown.add(option);
});
}

function getCSRFToken() {
const metaTag = document.querySelector('meta[name="csrf-token"]');
if (metaTag) {
Expand Down Expand Up @@ -510,14 +576,17 @@ function createNewAppointment(dateInput) {

async function showCreateAppointmentModal(defaultStartTime, formattedDate) {
const servicesDropdown = await populateServices(null, false);
const staffDropdown = await populateStaffMembers(null, false);
let staffDropdown = null;
if (isUserSuperUser) {
staffDropdown = await populateStaffMembers(null, false);
}
servicesDropdown.id = "serviceSelect";
servicesDropdown.disabled = false; // Enable dropdown

document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate);

adjustCreateAppointmentModalButtons();
AppState.isCreating = true;
AppStateProxy.isCreating = true;
$('#eventDetailsModal').modal('show');
}

Expand Down Expand Up @@ -581,7 +650,10 @@ async function showEventModal(eventId = null, isEditMode, isCreatingMode = false
if (!appointment) return;

const servicesDropdown = await getServiceDropdown(appointment.service_id, isEditMode);
const staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
let staffDropdown = null;
if (isUserSuperUser) {
staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode);
}

document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown);
adjustModalButtonsVisibility(isEditMode, isCreatingMode);
Expand All @@ -608,11 +680,11 @@ function adjustModalButtonsVisibility(isEditMode, isCreatingMode) {
function toggleEditMode() {
const modal = document.getElementById("eventDetailsModal");
const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected));
AppState.isCreating = false; // Turn off creating mode
AppStateProxy.isCreating = false; // Turn off creating mode

// Proceed only if an appointment is found
if (appointment) {
AppState.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state
AppStateProxy.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state
updateModalUIForEditMode(modal, AppState.isEditingAppointment);
} else {
console.error("Appointment not found!");
Expand Down
7 changes: 2 additions & 5 deletions appointment/templates/administration/staff_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
const updateApptDateURL = "{% url 'appointment:update_appt_date_time' %}";
const validateApptDateURL = "{% url 'appointment:validate_appointment_date' %}";
const isUserStaffAdminURL = "{% url 'appointment:is_user_staff_admin' %}";
const isUserSuperUser = "{{ is_superuser }}";
const isUserSuperUser = "{{ is_superuser }}" === "True";
</script>
<script>
{# Text for translation #}
Expand All @@ -341,15 +341,12 @@
<script src="{% static 'js/js-utils.js' %}"></script>

<script>
console.log("isUserSuperUser", isUserSuperUser)

function createCommonInputFields(appointment, servicesDropdown, isEditMode, defaultStartTime, staffDropdown) {
const startTimeValue = isEditMode ? moment(appointment.start_time).format('HH:mm:ss') : defaultStartTime;
const disabledAttribute = isEditMode ? '' : 'disabled';

const isSuperuser = isUserSuperUser === "True";
let superuserInputField = '';
if (isSuperuser) {
if (isUserSuperUser) {
superuserInputField = `
<div class="flex-container-appt">
<label>{% trans 'Staff Member' %}:</label>
Expand Down
6 changes: 2 additions & 4 deletions appointment/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def test_fetch_service_list_for_staff(self):
)

def test_fetch_service_list_for_staff_no_staff_member_instance(self):
"""Test that a superuser without a StaffMember instance receives an appropriate error message."""
"""Test that a superuser without a StaffMember instance receives no inappropriate error message."""
self.need_superuser_login()

# Ensure the superuser does not have a StaffMember instance
Expand All @@ -349,11 +349,9 @@ def test_fetch_service_list_for_staff_no_staff_member_instance(self):
response = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

# Check the response status code and content
self.assertEqual(response.status_code, 400)
self.assertEqual(response.status_code, 200)
response_data = response.json()
self.assertIn('message', response_data)
self.assertEqual(response_data['message'], _("You're not a staff member. Can't perform this action !"))
self.assertFalse(response_data['success'])

def test_fetch_service_list_for_staff_no_services_offered(self):
"""Test fetching services for a staff member who offers no services."""
Expand Down
24 changes: 20 additions & 4 deletions appointment/views_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ def get_user_appointments(request, response_type='html'):
}
context = get_generic_context_with_extra(request=request, extra=extra_context)
# if appointment is empty and user doesn't have a staff-member instance, put a message
if not appointments and not StaffMember.objects.filter(user=request.user).exists() and request.user.is_staff:
# TODO: Refactor this logic, it's not clean
if not appointments and not StaffMember.objects.filter(
user=request.user).exists() and not request.user.is_superuser:
messages.error(request, _("User doesn't have a staff member instance. Please contact the administrator."))
return render(request, 'administration/staff_index.html', context)

Expand Down Expand Up @@ -226,10 +228,12 @@ def add_or_update_staff_info(request, user_id=None):
return render(request, 'administration/manage_staff_member.html', context)


# TODO: Refactor this function, handle the different cases better.
@require_user_authenticated
@require_staff_or_superuser
def fetch_service_list_for_staff(request):
appointment_id = request.GET.get('appointmentId')
staff_id = request.GET.get('staff_id')
if appointment_id:
# Fetch services for a specific appointment (edit mode)
if request.user.is_superuser:
Expand All @@ -241,14 +245,23 @@ def fetch_service_list_for_staff(request):
if not Appointment.objects.filter(id=appointment_id,
appointment_request__staff_member=staff_member).exists():
return json_response(_("You do not have permission to access this appointment."), status_code=403)
services = list(staff_member.get_services_offered().values('id', 'name'))
elif staff_id:
# Fetch services for the specified staff member (new mode based on staff member selection)
staff_member = get_object_or_404(StaffMember, id=staff_id)
services = list(staff_member.get_services_offered().values('id', 'name'))
else:
# Fetch all services for the staff member (create mode)
try:
staff_member = StaffMember.objects.get(user=request.user)
services = list(staff_member.get_services_offered().values('id', 'name'))
except StaffMember.DoesNotExist:
return json_response(_("You're not a staff member. Can't perform this action !"), status=400, success=False)
if not request.user.is_superuser:
return json_response(_("You're not a staff member. Can't perform this action !"), status=400,
success=False)
else:
services = list(Service.objects.all().values('id', 'name'))

services = list(staff_member.get_services_offered().values('id', 'name'))
if len(services) == 0:
return json_response(_("No services offered by this staff member."), status=404, success=False,
error_code=ErrorCode.SERVICE_NOT_FOUND)
Expand Down Expand Up @@ -536,4 +549,7 @@ def is_user_staff_admin(request):
StaffMember.objects.get(user=user)
return json_response(_("User is a staff member."), custom_data={'is_staff_admin': True})
except StaffMember.DoesNotExist:
return json_response(_("User is not a staff member."), custom_data={'is_staff_admin': False})
# if superuser, all rights are granted even if not a staff member
if not user.is_superuser:
return json_response(_("User is not a staff member."), custom_data={'is_staff_admin': False})
return json_response(_("User is a superuser."), custom_data={'is_staff_admin': True})