feat: add survey

This commit is contained in:
2026-08-20 15:44:56 +03:30
parent 27413ae827
commit ffdae46038
3 changed files with 65 additions and 0 deletions
@@ -6,6 +6,7 @@
use App\Models\SurveyAnswer;
use App\Models\SurveyQuestion;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
@@ -56,6 +57,62 @@ public function adminShow($id)
return response()->json($question);
}
// ADMIN: paginated list of users with their survey stats (age, gender, answers count).
public function adminUsers(Request $request)
{
$perPage = max(1, min((int) $request->input('per_page', 20), 100));
$users = User::select('id', 'first_name', 'last_name', 'age', 'gender', 'email', 'mobile', 'created_at', 'identifier')
->withCount(['surveyAnswers as answers_count'])
->withCount(['surveyAnswers as questions_answered' => function ($q) {
$q->select(DB::raw('COUNT(DISTINCT survey_question_id)'));
}])
->orderByDesc('created_at')
->paginate($perPage);
return response()->json($users);
}
// ADMIN: single user profile with all their survey answers grouped by question.
// Accepts either local user ID or approagency identifier (UUID).
public function adminUserShow($identifier)
{
$user = User::where('id', $identifier)
->orWhere('identifier', $identifier)
->select('id', 'first_name', 'last_name', 'age', 'gender', 'birthday', 'email', 'mobile', 'created_at')
->withCount(['surveyAnswers as answers_count'])
->withCount(['surveyAnswers as questions_answered' => function ($q) {
$q->select(DB::raw('COUNT(DISTINCT survey_question_id)'));
}])
->first();
if (!$user) {
abort(404);
}
$answers = SurveyAnswer::where('user_id', $user->id)
->with(['question.options', 'option'])
->get()
->groupBy('survey_question_id')
->map(function ($group) {
$question = $group->first()->question;
return [
'question_id' => $question->id,
'question' => $question->question,
'type' => $question->type,
'answers' => $group->map(fn ($a) => [
'option_id' => $a->survey_option_id,
'label' => $a->option->label,
])->values(),
];
})
->values();
$user->survey_answers = $answers;
return response()->json($user);
}
// ADMIN: clean aggregated results — per option vote counts & percentages, no raw rows.
// Pass an $id for one question, omit it for all questions.
public function adminAnalytics($id = null)
+5
View File
@@ -143,4 +143,9 @@ public function savedMedia()
return $this->belongsToMany(Media::class, 'saved_media')->withTimestamps();
}
public function surveyAnswers()
{
return $this->hasMany(SurveyAnswer::class);
}
}
+3
View File
@@ -166,6 +166,9 @@
Route::get('/admin/survey-questions/{id}/analytics', [SurveyQuestionController::class, 'adminAnalytics']);
Route::get('/admin/survey-questions', [SurveyQuestionController::class, 'adminIndex']);
Route::get('/admin/survey-questions/{id}', [SurveyQuestionController::class, 'adminShow']);
// User list + detail for survey admin
Route::get('/admin/survey-users', [SurveyQuestionController::class, 'adminUsers']);
Route::get('/admin/survey-users/{id}', [SurveyQuestionController::class, 'adminUserShow']);
});
// User: answer + read questions with their own answers only.
Route::get('/survey-questions/suggested-media', [SurveyQuestionController::class, 'suggestedMedia']);