Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#115 - CRUD for administrators frontend #109

Draft
wants to merge 87 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
599e3f3
create basic crud layout
AmonDeShir Oct 21, 2024
8c831c0
refactor: decouple from 'demo' and '87-login-page-improvements' branches
JokeUrSelf Oct 14, 2024
e9c5ba3
fix: duration input is cut when expanding the test
Oct 18, 2024
2df9491
fix: duration can be set to number the exceeds the max limit
JokeUrSelf Oct 18, 2024
02d21cd
fix: remove copying for multiple quizzes
JokeUrSelf Oct 18, 2024
44d976f
fix: delete button deletes all questions
JokeUrSelf Oct 18, 2024
ecce6b4
style: answer check button color and position
JokeUrSelf Oct 18, 2024
9cbc5fb
style: create new answers with empty content
JokeUrSelf Oct 18, 2024
27caa34
build: update packages
JokeUrSelf Oct 18, 2024
6289fa1
fix: linting
JokeUrSelf Oct 18, 2024
b590f15
chore: merge button logic
JokeUrSelf Oct 18, 2024
d2d0d0f
fix: remove time limits for datepicker
JokeUrSelf Oct 18, 2024
6729c1e
fix: error message is not fully visible
JokeUrSelf Oct 18, 2024
6b3009f
fix: `schedule_at` error message contains untranslated word 'now'
JokeUrSelf Oct 18, 2024
71d75d2
fix: wrong interpritation of time after quiz update
JokeUrSelf Oct 18, 2024
ef5c86a
style: add animation to quizzes occurence
JokeUrSelf Oct 18, 2024
ba61f3c
format: remove unused import
JokeUrSelf Oct 18, 2024
d0d09b7
style: add animations for icon-buttons
JokeUrSelf Oct 19, 2024
d8f7f79
apply suggestion: change 'Stworzony' na 'Utworzony'
JokeUrSelf Oct 19, 2024
768c34a
fix: sorting isn' t preserved after page reload
JokeUrSelf Oct 19, 2024
16e7702
fix: deletion alert doesn't close when clicking "Usuń"
JokeUrSelf Oct 19, 2024
3b6b0ac
chore: preserve state and scroll in request components by default
JokeUrSelf Oct 19, 2024
6db15d5
fix: seeder creates quizzes that can't exist
JokeUrSelf Oct 19, 2024
8d27ffb
feat: add placeholders for empty textareas
JokeUrSelf Oct 20, 2024
d1af3e9
fix: "Zaproś uczestników" redirects to entry that doesn't yet exist
JokeUrSelf Oct 21, 2024
949ad49
fix: typo in "Zaproś uczestników"
JokeUrSelf Oct 21, 2024
9f5255a
feat: add sorting by modification
JokeUrSelf Oct 21, 2024
c3a8e97
feat: make pre-publish validation message more specific
JokeUrSelf Oct 21, 2024
5b765ff
fix: publish button doesn't switch its state
JokeUrSelf Oct 21, 2024
b81932f
feat: add " - kopia" to the end of the tile of a copied quiz
JokeUrSelf Oct 21, 2024
b3e5014
format: `"` to `'`
JokeUrSelf Oct 21, 2024
6bc7c03
fix: sorting state is preserved between sessions
JokeUrSelf Oct 22, 2024
62d6cbe
style: change 'Nie można oddać testu.' to 'Nie można opublikować testu.'
JokeUrSelf Oct 22, 2024
1f19208
refactor: suggestions from code review
JokeUrSelf Oct 25, 2024
e376e2b
chore: add archieved quizzes to seeder
JokeUrSelf Oct 29, 2024
51e8afa
build: update packages
JokeUrSelf Nov 7, 2024
2445ab1
feat: add dropdown pointer positioning
JokeUrSelf Nov 7, 2024
e383a25
Merge branch 'main' of https://github.com/blumilksoftware/interns2024…
JokeUrSelf Nov 7, 2024
874df44
Merge branch 'main' into 26-admin-panel-for-managing-tests-frontend
JokeUrSelf Nov 8, 2024
6428de8
fix: quiz title underline
JokeUrSelf Nov 8, 2024
be7e0bb
Merge branch '26-admin-panel-for-managing-tests-frontend' of https://…
AmonDeShir Nov 8, 2024
46d26e0
fix: duration input doesn't resize fully
JokeUrSelf Nov 8, 2024
87005a7
feat: error handle empty question
JokeUrSelf Nov 9, 2024
e877fce
refactor: link button styling
JokeUrSelf Nov 9, 2024
e219f2f
create warning message box
AmonDeShir Nov 9, 2024
2063823
move sorting logic to a separate helper
AmonDeShir Nov 9, 2024
8d41573
fix: cloning doesn't add data to database
JokeUrSelf Nov 9, 2024
38b669b
fix: error highliting on question deletion reappears on the next ques…
JokeUrSelf Nov 9, 2024
b4740c9
feat: automatically remove empty answers
JokeUrSelf Nov 9, 2024
df8d86a
fix: revert update logic
JokeUrSelf Nov 9, 2024
85734d9
create basic crud for schools
AmonDeShir Nov 9, 2024
7f36b82
Merge branch '26-admin-panel-for-managing-tests-frontend' of https://…
AmonDeShir Nov 9, 2024
f90179f
simplify quizzes page
AmonDeShir Nov 9, 2024
c9eaa29
fix linter errors
AmonDeShir Nov 9, 2024
820d24e
fix code style
AmonDeShir Nov 9, 2024
ddfbc8d
rename value to address
AmonDeShir Nov 9, 2024
41cf1a7
add regon to school
AmonDeShir Nov 9, 2024
bc14211
Merge branch 'main' of https://github.com/blumilksoftware/interns2024…
AmonDeShir Nov 9, 2024
d5bf673
fix code style
AmonDeShir Nov 9, 2024
0f8bebc
add lintf
AmonDeShir Nov 10, 2024
95b2ba8
create mobile version
AmonDeShir Nov 10, 2024
ee7d7b3
add newItem slot
AmonDeShir Nov 10, 2024
7a006d5
remove limit from school controller
AmonDeShir Nov 10, 2024
a8d6014
create CrudInput
AmonDeShir Nov 10, 2024
00cb5a9
fix InputWrapper
AmonDeShir Nov 10, 2024
073b20a
remove force-full-screen-nav
AmonDeShir Nov 10, 2024
6c9d414
add prop pointer-position to Dropdown
AmonDeShir Nov 10, 2024
55d1251
change import button icon
AmonDeShir Nov 10, 2024
a152140
improve code style
AmonDeShir Nov 10, 2024
5c047d7
add comments explaining textarea height reset
AmonDeShir Nov 10, 2024
d5daa9d
Replace ButtonFrame with Button
AmonDeShir Nov 10, 2024
3a9f5af
allow manual implementation of the 'New Item' button
AmonDeShir Nov 10, 2024
ac1b740
improve ButtonFrame
AmonDeShir Nov 10, 2024
add105f
fix code style
AmonDeShir Nov 10, 2024
12a21d6
move resize none to vDynamicTextAreaHeight.ts
AmonDeShir Nov 11, 2024
c16daea
fix InputWrapper
AmonDeShir Nov 11, 2024
abc8723
add padding to ButtonFrame
AmonDeShir Nov 11, 2024
7331358
fix linter errors
AmonDeShir Nov 11, 2024
18481ba
consider placeholder when calculating input width
AmonDeShir Nov 11, 2024
ed3743f
add fix-all
AmonDeShir Nov 11, 2024
f286ca3
update phony
AmonDeShir Nov 11, 2024
f1540e5
implement frontend for UsersPanel.vue
AmonDeShir Nov 11, 2024
7fb771e
fix new item mode
AmonDeShir Nov 11, 2024
acec9f0
Merge branch '50-crud-for-schools-frontend' of https://github.com/blu…
AmonDeShir Nov 11, 2024
c0d7d0a
fix CrudInput
AmonDeShir Nov 11, 2024
e7174fc
fix linter errors
AmonDeShir Nov 11, 2024
24e3385
implement frontend for admin CRUD
AmonDeShir Nov 11, 2024
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
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ test-specific:
fix:
@docker compose --file ${DOCKER_COMPOSE_FILE} exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" ${DOCKER_COMPOSE_APP_CONTAINER} bash -c 'composer csf'

lintf:
@docker compose --file ${DOCKER_COMPOSE_FILE} exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" ${DOCKER_COMPOSE_APP_CONTAINER} bash -c 'npm run lintf'

fix-all: fix lintf

analyse:
@docker compose --file ${DOCKER_COMPOSE_FILE} exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" ${DOCKER_COMPOSE_APP_CONTAINER} bash -c 'composer analyse'

Expand All @@ -66,4 +71,4 @@ queue:
create-test-db:
@docker compose --file ${DOCKER_COMPOSE_FILE} exec ${DOCKER_COMPOSE_DATABASE_CONTAINER} bash -c 'createdb --username=${DATABASE_USERNAME} ${TEST_DATABASE_NAME} &> /dev/null && echo "Created database for tests (${TEST_DATABASE_NAME})." || echo "Database for tests (${TEST_DATABASE_NAME}) exists."'

.PHONY: init check-env-file build run stop restart shell shell-root test fix create-test-db queue analyse dev
.PHONY: init check-env-file build run stop restart shell shell-root test test-specific fix lintf fix-all create-test-db queue analyse dev
15 changes: 15 additions & 0 deletions app/DTO/SchoolDTO.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class SchoolDTO
{
public function __construct(
public string $name,
public string $regon,
public string $city,
public string $street,
public string $buildingNumber,
Expand All @@ -19,6 +20,7 @@ public static function createFromArray(array $data): self
{
$member = new self(
$data["nazwa"],
$data["regon"],
$data["miejscowosc"],
$data["ulica"],
$data["numerBudynku"],
Expand All @@ -28,4 +30,17 @@ public static function createFromArray(array $data): self

return $member;
}

public function toArray(): array
{
return [
"name" => $this->name,
"regon" => $this->regon,
"city" => $this->city,
"street" => $this->street,
"building_number" => $this->buildingNumber,
"apartment_number" => $this->apartmentNumber,
"zip_code" => $this->zipCode,
];
}
}
3 changes: 2 additions & 1 deletion app/Http/Controllers/SchoolsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public function store(SchoolRequest $request): RedirectResponse
School::query()->create($request->validated());

return redirect()
->back();
->back()
->with("status", "Szkoła została dodana.");
}

public function update(SchoolRequest $request, School $school): RedirectResponse
Expand Down
6 changes: 4 additions & 2 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ class UserController extends Controller
public function index(Request $request): Response
{
$users = User::query()->role("user")->with("school")->orderBy("id")->get();
$schools = School::query()->orderBy("id")->get();

return Inertia::render("Admin/UsersPanel", [
"users" => UserResource::collection($users),
"schools" => SchoolResource::collection($schools),
]);
}

Expand All @@ -43,7 +45,7 @@ public function update(UserRequest $request, User $user): RedirectResponse

return redirect()
->route("admin.users.index")
->with("success", "Użytkownik zaktualizowany pomyślnie.");
->with("status", "Użytkownik zaktualizowany pomyślnie.");
}

public function anonymize(User $user): RedirectResponse
Expand All @@ -58,6 +60,6 @@ public function anonymize(User $user): RedirectResponse

return redirect()
->back()
->with("success", "Dane użytkownika zostały zanonimizowane.");
->with("status", "Dane użytkownika zostały zanonimizowane.");
}
}
18 changes: 17 additions & 1 deletion app/Http/Requests/SchoolRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ public function authorize(): bool
return true;
}

public function prepareForValidation(): void
{
if ($this->has("buildingNumber")) {
$this->merge(["building_number" => $this->input("buildingNumber")]);
}

if ($this->has("apartmentNumber")) {
$this->merge(["apartment_number" => $this->input("apartmentNumber")]);
}

if ($this->has("zipCode")) {
$this->merge(["zip_code" => $this->input("zipCode")]);
}
}

/**
* @return array<string, ValidationRule|array|string>
*/
Expand All @@ -22,9 +37,10 @@ public function rules(): array
return [
"name" => ["required", "string"],
"city" => ["required", "string"],
"regon" => ["required", "string"],
"street" => ["required", "string"],
"building_number" => ["required", "string"],
"apartment_number" => ["string"],
"apartment_number" => ["string", "nullable"],
"zip_code" => ["required", "string"],
];
}
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Resources/SchoolResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@ public function toArray(Request $request): array
return [
"id" => $this->id,
"name" => $this->name,
"regon" => $this->regon,
"city" => $this->city,
"street" => $this->street,
"buildingNumber" => $this->building_number,
"apartmentNumber" => $this->apartment_number,
"zipCode" => $this->zip_code,
"numberOfStudents" => $this->users()->count(),
"createdAt" => $this->created_at,
"updatedAt" => $this->updated_at,
];
}
}
2 changes: 2 additions & 0 deletions app/Http/Resources/UserResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public function toArray(Request $request): array
"isAnonymized" => $this->is_anonymized,
"isAdmin" => $this->hasRole("admin"),
"isSuperAdmin" => $this->hasRole("super_admin"),
"createdAt" => $this->created_at,
"updatedAt" => $this->updated_at,
];
}
}
10 changes: 10 additions & 0 deletions app/Models/School.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

/**
* @property int $id
* @property string $regon
* @property string $name
* @property string $city
* @property string $street
Expand All @@ -18,17 +21,24 @@
* @property string $zip_code
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Collection<User> $users
*/
class School extends Model
{
use HasFactory;

protected $fillable = [
"name",
"regon",
"city",
"street",
"building_number",
"apartment_number",
"zip_code",
];

public function users(): HasMany
{
return $this->hasMany(User::class);
}
}
13 changes: 4 additions & 9 deletions app/Services/GetSchoolDataService.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,10 @@ protected function fetchSchools(Voivodeship $voivodeship, array $schoolTypes): C
*/
protected function store(Collection $schools): void
{
foreach ($schools as $school) {
School::firstOrCreate([
"name" => $school->name,
"city" => $school->city,
"street" => $school->street,
"building_number" => $school->buildingNumber,
"apartment_number" => $school->apartmentNumber,
"zip_code" => $school->zipCode,
]);
foreach ($schools as $dto) {
$school = School::query()->firstOrNew(["regon" => $dto->regon]);
$school->fill($dto->toArray());
$school->save();
}
}
}
3 changes: 3 additions & 0 deletions database/factories/SchoolFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use App\Models\School;
use Illuminate\Database\Eloquent\Factories\Factory;

use function str;

/**
* @extends Factory<School>
*/
Expand All @@ -21,6 +23,7 @@ public function definition(): array
{
return [
"name" => fake()->company(),
"regon" => str(fake()->randomNumber(9)),
"city" => fake()->city(),
"street" => fake()->streetName(),
"building_number" => fake()->buildingNumber(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public function up(): void
{
Schema::create("schools", function (Blueprint $table): void {
$table->bigIncrements("id")->unique();
$table->string("regon");
$table->string("name");
$table->string("city");
$table->string("street");
Expand Down
48 changes: 48 additions & 0 deletions resources/js/Helpers/Sorter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {onMounted, ref, type Ref, watch} from 'vue'
import dayjs from 'dayjs'
import {keysWrapper} from '@/Helpers/KeysManager'

function setSorter<T extends Sortable>(sorter: Ref<Sorter<T> | undefined>, sorterName: string, type: 'name' | 'title' | 'creationDate' | 'modificationDate', desc = false) {
sessionStorage.setItem(`${sorterName}SorterPreference`, JSON.stringify([type, desc]))

sorter.value = (a: T, b: T) => {
if (desc) {
[a, b] = [b, a]
}

const comparators = {
title: () => ('title' in a && 'title' in b) ? a.title.localeCompare(b.title) : 0,
name: () => ('name' in a && 'name' in b) ? a.name.localeCompare(b.name) : 0,
creationDate: () => dayjs(a.createdAt).diff(dayjs(b.createdAt)),
modificationDate: () => dayjs(a.updatedAt).diff(dayjs(b.updatedAt)),
}

return comparators[type]()
}
}

export function useSorter<T extends Sortable>(sorterName: string, data: () => T[], options: SortOptionConstructor[]): [Ref<T[], T[]>, SortOption[]] {
const items = ref<T[]>(data()) as Ref<T[]>
const sorter = ref<(a: T, b: T) => number>()
const sortOptions = keysWrapper(options.map((option) => ({
text: option.text,
action: () => setSorter(sorter, sorterName, option.type, option.desc) }
)))

onMounted(() => {
const savedSorter = sessionStorage.getItem(`${sorterName}SorterPreference`)
const [type, desc] = savedSorter ? JSON.parse(savedSorter) : ['modificationDate', true]
setSorter(sorter, sorterName, type, desc)
})

watch(
[data, sorter],
([newData, sorter]) => {
items.value = newData
items.value.sort(sorter)
},
{ immediate: true },
)

return [items, sortOptions]
}
14 changes: 11 additions & 3 deletions resources/js/Helpers/vDynamicInputWidth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ function initDynamicWidthCalc(input:HTMLInputElement & { _calculateDynamicWidth:
}

function calculateDynamicWidth(input:HTMLInputElement, binding?:DirectiveBinding<boolean>){
if (binding?.value === false) return
if (binding?.value === false) {
return
}

const context = canvas.getContext('2d')
if (!context) return

if (!context) {
return
}

const { fontWeight, fontSize, fontFamily } = window.getComputedStyle(input)
context.font = `${fontWeight} ${fontSize} ${fontFamily}`
const width = `${context.measureText(input.value).width}px`
const width = `${context.measureText(input.value || input.placeholder).width}px`

input.style.width = `clamp(1.1rem,${width},100%)`
}

Expand Down
35 changes: 35 additions & 0 deletions resources/js/Helpers/vDynamicTextAreaHeight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type DirectiveBinding } from 'vue'

function initDynamicHeightCalc(input:HTMLTextAreaElement & { _calculateDynamicHeight:() => void }, binding?:DirectiveBinding<boolean>) {
input._calculateDynamicHeight = () => calculateDynamicHeight(input, binding)

document.fonts.addEventListener('loadingdone', input._calculateDynamicHeight)
input.addEventListener('transitionend', input._calculateDynamicHeight)

input._calculateDynamicHeight()
input.style.resize = 'none'
}

function calculateDynamicHeight(input:HTMLTextAreaElement, binding?:DirectiveBinding<boolean>){
if (binding?.value === false) {
return
}

// Reset the height to a minimal value to refresh scrollHeight.
// This ensures the textarea will shrink when text is removed.
input.style.height = '5px'
input.style.height = `${input.scrollHeight}px`
}

function removeDynamicHeightCalc(input:HTMLTextAreaElement & { _calculateDynamicHeight:() => void }) {
document.fonts.removeEventListener('loadingdone', input._calculateDynamicHeight)
input.removeEventListener('transitionend', input._calculateDynamicHeight)
}

const vDynamicInputHeight = {
mounted: initDynamicHeightCalc,
updated: calculateDynamicHeight,
unmounted: removeDynamicHeightCalc,
}

export default vDynamicInputHeight
Loading
Loading