Add Question Logic

User
@extends('layouts.admin')
@section('plugins.Summernote', true)
@section('plugins.icheckBootstrap', true)
@section('plugins.TempusDominusBs4', true)

{{– Customize layout sections –}}
@section('subtitle', __('Edit – Survey'))

{{– Content body: main page content –}}
@section('content_body')

{{ __('Edit Survey') }}

@if($errors->any())

@foreach ($errors->all() as $error)
{{ $error }}
@endforeach

@endif
@if(session()->has('message.level'))

{!! session('message.content') !!}

@endif

Loading…

{{ __('Loading survey creation…') }}

1. Survey

2. Pillers

3. Questions

{{ __('Create Survey') }}

Name

@{{ surveyError }}

{{ __('Allow comments on Survey') }}
{{ __('Allow comments on Pillers') }}
{{ __('Mark as External') }}

{{ __('Save and Next') }}

{{ __('Edit Pillers') }}

{{ __('Piller') }} @{{ index + 1 }}

{{ __('Title') }} (@{{ lang.code.toUpperCase() }})

{{ __('Weight') }}

{{ __('Position') }}

{{ __('Add More Pillers') }}

{{ __('Save and Next') }}

{{ __('Add Questions') }}

{{ __('Select Piller') }}

{{ __('– Select Piller –') }}

@{{ pill.titles.en }}

@{{ getPillerTitle(selectedPillerId) }}

{{ __('Title') }} (@{{ lang.code.toUpperCase() }})

{{ __('Position') }}

{{ __('Add More Question') }}

{{ __('Submit All Questions') }}

@stop

{{– Push extra scripts –}}
@push('js')

<script>
window.surveyData = @json($survey);
   console.log(window.surveyData);
    window.pillarsData = @json($pillars);
    console.log(window.pillarsData);
    window.questionsData = @json($questions);
    console.log(window.questionsData);
    window.availableLanguages = @json(getLanguages());
    const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
</script>
<script>
import { createApp } from 'https://unpkg.com/vue@3.5.13/dist/vue.esm-browser.prod.js'
  document.addEventListener('DOMContentLoaded', () => {
    const app = createApp({
    data() {
      return {
        message: 'Hello Vue!',
        loading: false,
        step: 1,
        survey: {
          name: window.surveyData.name || '',
          options: []
        },
        surveyError: '',
        survey_id: window.surveyData.id || null,
        availableLanguages: window.availableLanguages || [],
        pillers: window.pillarsData || [{
          titles: {},
          weight: '',
          position: ''
        }],
        selectedPillerId: '',
        questions: window.questionsData || {},
      }
    },
    mounted() {
     
      // Initialize options from booleans
  if (window.surveyData?.allow_comments_survey) {
    this.survey.options.push('allow_comments');
  }
  if (window.surveyData?.allow_comments_pillars) {
    this.survey.options.push('allow_comments_on_pillers');
  }
  if (window.surveyData?.external) {
    this.survey.options.push('mark_as_external');
  }
  // Init first piller titles
  if (this.pillers.length > 0) {
    this.pillers.forEach(piller => {
      this.availableLanguages.forEach(lang => {
        if (!piller.titles[lang.code]) {
          piller.titles[lang.code] = '';
        }
      });
    });
  }
},
    methods: {
      onPillerChange() {
    if (this.selectedPillerId && !this.questions[this.selectedPillerId]) {
      this.questions[this.selectedPillerId] = [];
    }
     // If there are no questions yet for this pillar, initialize with one
  if (!this.questions[this.selectedPillerId] || this.questions[this.selectedPillerId].length === 0) {
    const newQuestion = {
      id: null, // in case you support editing
      titles: {},
      position: ''
    };

    this.availableLanguages.forEach(lang => {
      newQuestion.titles[lang.code] = '';
    });

    this.questions[this.selectedPillerId] = [newQuestion];
  }
  },
      getPillerTitle(id) {
    const piller = this.pillers.find(p => p.id == id);
    return piller ? piller.titles.en : '';
},
addQuestion() {
    if (!this.questions[this.selectedPillerId]) {
        this.questions[this.selectedPillerId] = [];
    }

    if (this.questions[this.selectedPillerId].length >= 10) return;

    const newQuestion = {
        titles: {},
        position: ''
    };

    this.availableLanguages.forEach(lang => {
        newQuestion.titles[lang.code] = '';
    });

    this.questions[this.selectedPillerId].push(newQuestion);
},

removeQuestion(index) {
    this.questions[this.selectedPillerId].splice(index, 1);
},
      goToStep(step) {
    if (step === 2 && !this.survey_id) return;
    if (step === 3 && this.pillers.every(p => !p.id)) return;
    this.step = step;
    if (step === 3 && !this.selectedPillerId && this.pillers.length > 0) {
    this.selectedPillerId = this.pillers[0].id;

    // Initialize questions array for selected pillar if not already present
    if (!this.questions[this.selectedPillerId]) {
      this.questions[this.selectedPillerId] = [];
    }
  }
  },
  addPiller() {
    if (this.pillers.length >= 5) return;
    const newPiller = {
      titles: {},
      weight: '',
      position: ''
    };
    this.availableLanguages.forEach(lang => {
      newPiller.titles[lang.code] = '';
    });
    this.pillers.push(newPiller);
  },
  removePiller(index) {
    this.pillers.splice(index, 1);
  },
  async submitSurvey() {
    
    this.surveyError = '';

    if (!this.survey.name) {
        this.surveyError = 'Survey name is required.';
        this.survey.name = window.surveyData?.name || '';
        return;
    }

    const data = {
        name: this.survey.name,
        allow_comments: this.survey.options.includes('allow_comments') ? 1 : 0,
        allow_comments_on_pillers: this.survey.options.includes('allow_comments_on_pillers') ? 1 : 0,
        mark_as_external: this.survey.options.includes('mark_as_external') ? 1 : 0,
    };
    this.loading = true;
    try {
    fetch(`{{ route('admin.surveys.update', ['survey' => ':id']) }}`.replace(':id', this.survey_id), {
        method: "PUT",
        headers: {
            "Content-Type": "application/json",
            "X-CSRF-TOKEN": csrfToken
        },
        body: JSON.stringify(data)
    })
    .then(res => res.json().then(json => ({ status: res.status, body: json })))
    .then(({ status, body }) => {
        if (status === 200 && body.success) {
            this.survey_id = body.survey_id;
            Swal.fire({
                icon: 'success',
                title: "{{ __('Survey saved successfully!') }}",
                showConfirmButton: false,
                timer: 1500
            });
            this.step = 2;
        } else {
            Swal.fire({
                icon: 'error',
                title: "{{ __('Validation Error') }}",
                text: body.message || "{{ __('Please check your input.') }}"
            });
        }
    })
    .catch(err => {
        Swal.fire({
            icon: 'error',
            title: "{{ __('Unexpected Error') }}",
            text: "{{ __('Something went wrong while saving the survey.') }}"
        });
    });
    } catch (error) {
      Swal.fire({
        icon: 'error',
        title: "{{ __('Unexpected Error') }}",
        text: "{{ __('Something went wrong while saving the survey.') }}"
      });
    } finally {
      this.loading = false;
    }
},

submitPillers() {
  if (this.pillers.length !== 5) {
    Swal.fire('Validation Error', 'You must add exactly 5 Pillers to continue.', 'warning');
    return;
  }

  // Validate each piller
  for (let [i, piller] of this.pillers.entries()) {
    for (let lang of this.availableLanguages) {
      if (!piller.titles[lang.code]) {
        Swal.fire('Validation Error', `Piller ${i + 1}: Title required for ${lang.title}`, 'error');
        return;
      }
    }
    if (!piller.weight || !piller.position) {
      Swal.fire('Validation Error', `Piller ${i + 1}: Weight and Position are required`, 'error');
      return;
    }
  }

  const data = {

    pillars: this.pillers
  };
  this.loading = true;
  try {
    fetch(`{{ route('admin.pillars.updateAll', ['survey' => ':id']) }}`.replace(':id', this.survey_id), {
      method: 'PUT',
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-TOKEN": csrfToken
    },
    body: JSON.stringify(data)
  })
  .then(res => res.json().then(json => ({ status: res.status, body: json })))
  .then(({ status, body }) => {
    if (status === 200 && body.success) {
      // Save returned piller_ids if needed
      this.pillers = body.pillers;
      this.selectedPillerId = this.pillers[0].id;
      Swal.fire({
        icon: 'success',
        title: "{{ __('Pillers saved successfully!') }}",
        showConfirmButton: false,
        timer: 1500
      });
      this.step = 3;
    } else {
      Swal.fire({
        icon: 'error',
        title: "{{ __('Validation Error') }}",
        text: body.message || "{{ __('Please check your input.') }}"
      });
    }
  })
  .catch(() => {
    Swal.fire({
      icon: 'error',
      title: "{{ __('Unexpected Error') }}",
      text: "{{ __('Something went wrong while saving the Pillers.') }}"
    });
  });
  } catch (error) {
    Swal.fire({
      icon: 'error',
      title: "{{ __('Unexpected Error') }}",
      text: "{{ __('Something went wrong while saving the Pillers.') }}"
    });
  } finally {
    this.loading = false;
  }
},

submitQuestions() {
    const payload = {
        questions: []
    };

    for (let piller of this.pillers) {
        const list = this.questions[piller.id] || [];

        if (list.length < 1) {
            Swal.fire('Validation Error', `Please add at least 1 question for "${piller.titles.en}"`, 'warning');
            return;
        }

        for (let q of list) {
            for (let lang of this.availableLanguages) {
                if (!q.titles[lang.code]) {
                    Swal.fire('Validation Error', `All languages required for a question in "${piller.titles.en}"`, 'error');
                    return;
                }
            }
            if (!q.position) {
                Swal.fire('Validation Error', `Position is required for a question in "${piller.titles.en}"`, 'error');
                return;
            }

            payload.questions.push({
                id: q.id || null,
                piller_id: piller.id,
                titles: q.titles,
                position: q.position
            });
        }
    }

    fetch("{{ route('admin.questions.updateAll', ['survey' => ':id']) }}".replace(':id', this.survey_id), {
        method: "PUT",
        headers: {
            "Content-Type": "application/json",
            "X-CSRF-TOKEN": csrfToken
        },
        body: JSON.stringify(payload)
    })
    .then(res => res.json().then(json => ({ status: res.status, body: json })))
    .then(({ status, body }) => {
        if (status === 200 && body.success) {
            Swal.fire({
                icon: 'success',
                title: "{{ __('Questions saved successfully!') }}",
                showConfirmButton: false,
                timer: 1500
            });
            window.location.href = "{{ route('admin.surveys.index') }}";
        } else {
            Swal.fire({
                icon: 'error',
                title: "{{ __('Validation Error') }}",
                text: body.message || "{{ __('Please check your input.') }}"
            });
        }
    })
    .catch(() => {
        Swal.fire({
            icon: 'error',
            title: "{{ __('Unexpected Error') }}",
            text: "{{ __('Something went wrong while saving the questions.') }}"
        });
    });
},

deletePiller(id, index) {
  if (!id) {
    // Not saved to backend yet, just remove from local list
    this.pillers.splice(index, 1);
    return;
  }

  Swal.fire({
    title: `{{ __('Are you sure?') }}`,
    text: `{{ __('This will permanently delete the pillar and its questions!') }}`,
    icon: 'warning',
    showCancelButton: true,
    confirmButtonColor: '#d33',
    confirmButtonText: `{{ __('Delete') }}`,
    cancelButtonText: `{{ __('Cancel') }}`
  }).then((result) => {
    if (result.isConfirmed) {
      fetch(`{{ route('admin.pillars.destroy', ['pillar' => ':id']) }}`.replace(':id', id), {
        method: 'DELETE',
        headers: {
          'X-CSRF-TOKEN': csrfToken
        }
      })
        .then(res => res.json())
        .then(data => {
          if (data.success) {
            this.pillers.splice(index, 1);
            delete this.questions[id]; // clean up related questions
            Swal.fire({
              icon: 'success',
              title: `{{ __('Deleted!') }}`,
              text: data.message,
              showConfirmButton: false,
              timer: 1500
            });
          } else {
            Swal.fire({
              icon: 'error',
              title: `{{ __('Error') }}`,
              text: data.message || 'Failed to delete pillar.',
              showConfirmButton: false,
              timer: 1500
            });
          }
        })
        .catch(() => {
          Swal.fire('Error', 'Something went wrong.', 'error');
        });
    }
  });
},
deleteQuestion(id, index) {
  if (!id) {
    // Not saved to backend yet, just remove from local list
    this.questions[this.selectedPillerId].splice(index, 1);
    return;
  }

  Swal.fire({
    title: `{{ __('Are you sure?') }}`,
    text: `{{ __('This will permanently delete the question!') }}`,
    icon: 'warning',
    showCancelButton: true,
    confirmButtonColor: '#d33',
    confirmButtonText: `{{ __('Delete') }}`,
    cancelButtonText: `{{ __('Cancel') }}`
  }).then((result) => {
    if (result.isConfirmed) {
      fetch(`{{ route('admin.questions.destroy', ['question' => ':id']) }}`.replace(':id', id), {
        method: 'DELETE',
        headers: {
          'X-CSRF-TOKEN': csrfToken
        }
      })
        .then(res => res.json())
        .then(data => {
          if (data.success) {
            this.questions[this.selectedPillerId].splice(index, 1);
            Swal.fire({
              icon: 'success',
              title: `{{ __('Deleted!') }}`,
              text: data.message,
              showConfirmButton: false,
              timer: 1500
            });
          } else {
            Swal.fire({
              icon: 'error',
              title: `{{ __('Error') }}`,
              text: data.message || `{{ __('Failed to delete question.') }}`,
              showConfirmButton: false,
              timer: 1500
            });
          }
        })
        .catch(() => {
          Swal.fire('Error', `{{ __('Something went wrong.') }}`, 'error');
        });
    }
  });
},


    }
  });
  app.mount('#surveyApp');
  // Hide loading spinner after mount
const loadingDiv = document.getElementById('vue-loading');
if (loadingDiv) {
  loadingDiv.classList.add('fade-out');
  setTimeout(() => loadingDiv.remove(), 500); // fully remove from DOM after fade
}
  });
</script>

@endpush
explain then addQuestion logic

Assistant
### `addQuestion` Logic Explanation

The `addQuestion` method is responsible for **adding a new question** to the selected piller in **Step 3** of the survey editing form.

Here's the breakdown of the logic:

addQuestion() {
    if (!this.questions[this.selectedPillerId]) {
        this.questions[this.selectedPillerId] = [];
    }

    if (this.questions[this.selectedPillerId].length >= 10) return;

    const newQuestion = {
        titles: {},
        position: ''
    };

    this.availableLanguages.forEach(lang => {
        newQuestion.titles[lang.code] = '';
    });

    this.questions[this.selectedPillerId].push(newQuestion);
}

### Step-by-step Logic:

1. **Check if Questions Exist for Selected Piller**:

if (!this.questions[this.selectedPillerId]) {
       this.questions[this.selectedPillerId] = [];
   }

– If the current selected piller (`selectedPillerId`) has no questions array yet, it initializes an empty array for it.

2. **Limit to 10 Questions**:

if (this.questions[this.selectedPillerId].length >= 10) return;

– If 10 or more questions already exist for the selected piller, the method exits to enforce a maximum of 10 questions per piller.

3. **Initialize New Question Object**:

const newQuestion = {
       titles: {},
       position: ''
   };

– A new question object is created. It contains:
– `titles`: an empty object to be filled with language-specific question titles.
– `position`: a field to define the order or position of the question.

4. **Add Title Field for Each Language**:

this.availableLanguages.forEach(lang => {
       newQuestion.titles[lang.code] = '';
   });

– For each language available (like `en`, `de`, etc.), a blank title is initialized inside `titles`.

5. **Push the New Question into the Array**:

this.questions[this.selectedPillerId].push(newQuestion);

– The fully prepared question object is added to the selected piller's question list.

### Purpose:

This method helps in dynamically managing multiple localized questions under a specific piller. It ensures:
– Multilingual support.
– Position management.
– Enforced limit to prevent UI overload or unexpected behavior.

Let me know if you want this to auto-initialize a default title or position, or bind it to a form submit!

User
how it create input boxes
Assistant
Great question!

The `addQuestion` method doesn’t **directly create input boxes**, but it **adds new data objects** to the `questions` array for a specific `piller_id`. Then Vue automatically re-renders the DOM and **generates input fields using `v-for`** in the template.

Let’s break down exactly how the input boxes are created:

### 🔁 This `v-for` in your template handles rendering:

<div v-for="(question, index) in questions[selectedPillerId]" :key="index" class="form-row align-items-end mb-2">
  <div v-for="lang in availableLanguages" :key="lang.code" class="col-md-4">
    <label>{{ __('Title') }} (@{{ lang.code.toUpperCase() }})</label>
    <input
      type="text"
      v-model="question.titles[lang.code]"
      class="form-control"
      :placeholder="`Enter title in ${lang.title}`">
  </div>

  <div class="col-md-2">
    <label>{{ __('Position') }}</label>
    <input type="number" v-model="question.position" class="form-control" placeholder="e.g. 1">
  </div>

  <div class="col-md-1">
    <button class="btn btn-danger btn-sm mt-4" @click="question.id ? deleteQuestion(question.id, index) : removeQuestion(index)">
      <i class="fa fa-trash"></i>
    </button>
  </div>
</div>

### 🔧 How it works:

1. `v-for="(question, index) in questions[selectedPillerId]"`
– Loops over all questions for the selected piller ID.

2. `v-for="lang in availableLanguages"` inside that
– For each question, it creates **one input field per language** (like English, German, etc.) bound to `question.titles[lang.code]`.

3. A numeric input for position:

<input type="number" v-model="question.position" class="form-control" placeholder="e.g. 1">

4. A remove button to delete that specific question.

### ⚡ Vue Reactivity:

As soon as `addQuestion()` pushes a new question object into `questions[selectedPillerId]`, Vue detects the change and updates the DOM, which renders the new set of input boxes for that new question.

Let me know if you want:
– A default position number auto-filled.
– Input validation per field.
– A button to clone an existing question.

User
private function surveyDataFromClientId($clientId)
{
$survey = Survey::with(['pillars'])->where('id', $clientId)->first();

$competenceId = Pillar::where('survey_id',$survey->id)->pluck('id')->toArray();

$questionsIds = SurveyQuestion::where('survey_id', $survey->id)->whereIn('competence_id',$competenceId)->pluck('question_id')->toArray();

$survey->survey_competences = $survey->survey_competences->unique('competence_id');

$survey->survey_competences->map(function ($sc) use ($questionsIds) {
$sc->questions = Question::whereIn('id', $questionsIds)
->where('competence_id', $sc->competence_id)
->get();
});

return $survey;
}
pillar has survey_id
question has piller id
$survey->survey_competences = $survey->survey_competences->unique('competence_id');
now pillars
private function surveyDataFromClientId($clientId)
{
$survey = Survey::with(['pillars'])->where('id', $clientId)->first();

$competenceId = Pillar::where('survey_id',$survey->id)->pluck('id')->toArray();

$questionsIds = SurveyQuestion::where('survey_id', $survey->id)->whereIn('competence_id',$competenceId)->pluck('question_id')->toArray();

$survey->survey_competences = $survey->survey_competences->unique('competence_id');

$survey->survey_competences->map(function ($sc) use ($questionsIds) {
$sc->questions = Question::whereIn('id', $questionsIds)
->where('competence_id', $sc->competence_id)
->get();
});

return $survey;
}
now adjust above

Assistant
Thanks for the clarification — you're transitioning from a `competence`-based setup to a `pillar`-based one.

Here's a revised version of your method where:

– `Pillar` is the primary relation with `survey_id`.
– `Question` is directly related to `pillar_id` (instead of `competence_id`).
– You're collecting `pillar_id`s and associating related `questions` with them.
– We cleanly attach questions to each `pillar`.

### ✅ Updated Method (pillar-based version):

private function surveyDataFromClientId($clientId)
{
    // Load survey with its pillars
    $survey = Survey::with(['pillars'])->where('id', $clientId)->firstOrFail();

    // Get pillar IDs from the survey
    $pillarIds = $survey->pillars->pluck('id')->toArray();

    // Get all related questions grouped by pillar_id
    $questions = Question::whereIn('pillar_id', $pillarIds)->get()->groupBy('pillar_id');

    // Map questions to each pillar
    $survey->pillars->map(function ($pillar) use ($questions) {
        $pillar->questions = $questions[$pillar->id] ?? collect();
    });

    return $survey;
}

### ✅ Explanation:

– `Survey::with(['pillars'])`: eager loads pillars.
– `$pillarIds`: IDs of all pillars related to the survey.
– `Question::whereIn('pillar_id', $pillarIds)`: fetches all related questions.
– `.groupBy('pillar_id')`: organizes questions into buckets per pillar.
– The `map()` attaches the grouped questions to each pillar using a new property `$pillar->questions`.

Let me know if:
– You want to preserve the original `survey_competences` structure.
– Questions need to be localized or include answers/responses.
– You want to format it for API output (e.g., using resources or arrays).

User
get question ids
$questionsIds = SurveyQuestion::where('survey_id', $survey->id)->whereIn('competence_id',$competenceId)->pluck('question_id')->toArray();
Assistant
Since you're now using **`Pillar`** instead of `Competence`, and **`Question`** has a `pillar_id` column, you should update that line to directly fetch question IDs based on the pillar IDs.

Here’s the adjusted version:

$questionIds = Question::whereIn('pillar_id', $pillarIds)->pluck('id')->toArray();

### Explanation:

– `pillar_id` is the new foreign key reference in the `questions` table.
– `$pillarIds` comes from:

$pillarIds = Pillar::where('survey_id', $survey->id)->pluck('id')->toArray();

So the full update context looks like:

$pillarIds = Pillar::where('survey_id', $survey->id)->pluck('id')->toArray();

$questionIds = Question::whereIn('pillar_id', $pillarIds)->pluck('id')->toArray();

Let me know if you want to include additional filtering (e.g., only active questions or localized data).

Leave a Comment

Your email address will not be published. Required fields are marked *