fix! исправлены обнаруженные баги в админке

This commit is contained in:
developer 2026-01-23 10:43:48 +08:00
parent ce78fad613
commit 55bf61a359
14 changed files with 153 additions and 60 deletions

View File

@ -44,6 +44,16 @@ public function edit(Company $company)
public function update(Request $request, Company $company)
{
$validated = $request->validate([
'name' => 'required',
'email' => "required|unique:companies,email,{$company->id}",
'phone' => "required|unique:companies,phone,{$company->id}",
],
[
'email.unique' => 'Указанная электронная почта уже существует',
'phone.unique' => 'Указанный номер телефона уже существует'
]
);
$company->update($request->only('name', 'email', 'phone'));
if ($request->token) {
$company->bitrixy->token = $request->token;

View File

@ -28,6 +28,7 @@ public function index(Request $request)
$complexes = $complexes->get();
return view('admin::complexes.index', [
'complexes' => $complexes,
'cities' => City::orderBy('name')->get(),
'filter' => $request->filter
]);
}
@ -48,7 +49,16 @@ public function update(Request $request, Complex $complex)
public function create(Request $request)
{
$city = Complex::create($request->only('name'));
$validated = $request->validate([
'name' => "required",
'city_id' => "required",
],
[
'name.required' => 'Необходимо указать название жилого комплекса',
'city_id.required' => 'Необходимо указать город'
]
);
$city = Complex::create($request->only('name', 'city_id'));
return to_route('admin.complexes');
}

View File

@ -9,7 +9,8 @@
@csrf
<div class="mb-3">
<label for="nameFormControl" class="form-label">Название</label>
<input type="text" class="form-control" id="nameFormControl" name="name" value="{{ $company->name }}">
<input type="text" class="form-control" id="nameFormControl" name="name"
value="{{ old('name', $company->name) }}">
@error('name')
<div class="text-danger">{{ $message }}</div>
@enderror
@ -18,17 +19,17 @@
<div class="mb-3">
<label for="emailFormControl" class="form-label">Email</label>
<input type="text" class="form-control" id="emailFormControl" name="email"
value="{{ $company->email }}">
value="{{ old('email', $company->email) }}">
@error('email')
<div class="text-danger">{{ $message }}</div>
<div class="text-danger d-none">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="phoneFormControl" class="form-label">Телефон</label>
<input type="text" class="form-control" id="phoneFormControl" name="phone"
value="{{ $company->phone }}">
<input type="tel" class="form-control" id="phoneFormControl" name="phone"
value="{{ old('phone', $company->phone) }}">
@error('phone')
<div class="text-danger">{{ $message }}</div>
<div class="text-danger d-none">{{ $message }}</div>
@enderror
</div>
</div>

View File

@ -16,7 +16,6 @@
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="cityFormControl" class="form-label">Город</label>
<select class="form-select" name="city_id" id="cityFormControl">

View File

@ -47,8 +47,8 @@
</td>
<td class="text-end">
<div class="dropdown" style="">
<button class="btn btn-light" type="button" id="dropdownMenuButton"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-light" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
@if ($complex->trashed())
<i class="bi bi-recycle"></i>
@else
@ -65,8 +65,7 @@
@else
<a class="dropdown-item"
href="{{ route('admin.complexes.edit', ['complex' => $complex]) }}">Редактировать</a>
<form method="post"
action="{{ route('admin.complexes.delete', ['complex' => $complex]) }}">
<form method="post" action="{{ route('admin.complexes.delete', ['complex' => $complex]) }}">
@csrf
<button class="dropdown-item" type="submit">Удалить</button>
</form>
@ -83,21 +82,28 @@
<!-- Modal -->
<div class="modal fade" id="createCityModal" tabindex="-1" aria-labelledby="createCityModalLabel" aria-hidden="true">
<form class="modal-dialog modal-dialog-centered" action="{{ route('admin.cities.create') }}" method="post">
<form class="modal-dialog modal-dialog-centered" action="{{ route('admin.complexes.create') }}" method="post">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="createCityModalLabel">Новый город</h1>
<h1 class="modal-title fs-5" id="createCityModalLabel">Новый жилой комплекс</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="my-3">
@csrf
<label for="cityNameInput" class="form-label">Введите название нового города</label>
<input class="form-control" type="text" id="cityNameInput" name="name" required>
@error('text')
<div class="text-danger">{{ $message }}</div>
@enderror
<div class="mb-3">
<label for="nameFormControl" class="form-label">Название</label>
<input type="text" class="form-control" id="nameFormControl" name="name"
value="{{ old('name') }}">
</div>
<div class="mb-3">
<label for="cityFormControl" class="form-label">Город</label>
<select class="form-select" name="city_id" id="cityFormControl">
@foreach ($cities as $city)
<option value="{{ $city->id }}" @if (old('city_id') == $city->id) selected @endif>
{{ $city->name }}
</option>
@endforeach
</select>
</div>
</div>

View File

@ -22,7 +22,7 @@
</div>
<div class="mb-3">
<label for="phoneFormControl" class="form-label">телефон</label>
<input type="text" class="form-control" id="phoneFormControl" name="phone"
<input type="tel" class="form-control" id="phoneFormControl" name="phone"
value="{{ $user->phone }}">
@error('phone')
<div class="text-danger">{{ $message }}</div>

View File

@ -100,7 +100,7 @@
</div>
<div class="my-3">
<label for="nameInput" class="form-label">Телефон</label>
<input class="form-control" type="text" id="phoneInput" name="phone" required>
<input class="form-control" type="tel" id="phoneInput" name="phone" required>
@error('phone')
<div class="text-danger">{{ $message }}</div>
@enderror

View File

@ -46,7 +46,7 @@
<div class="mb-3">
<label for="agentPhone" class="form-label">Телефон</label>
<input type="text" class="form-control" id="agentPhone" name="phone">
<input type="tel" class="form-control" id="agentPhone" name="phone">
</div>
<div class="mb-3 form-check form-switch">

View File

@ -46,7 +46,7 @@
<div class="mb-3">
<label for="adminPhone" class="form-label">Телефон</label>
<input type="text" class="form-control" id="adminPhone" name="phone">
<input type="tel" class="form-control" id="adminPhone" name="phone">
</div>
<div class="mb-3 form-check form-switch">

View File

@ -24,7 +24,7 @@
if($json['total_commits'] > 0)
{
$result = shell_exec("cd /var/www/lk && git reset --hard HEAD && git pull && php artisan migrate");
$result = shell_exec("cd /var/www/lk && git reset --hard HEAD && git pull && php artisan migrate && npm run build");
echo "<p>$result</p>";
}

View File

@ -0,0 +1,81 @@
function initPhoneMask(selector = 'input[type="tel"]') {
const format = (digits) => {
// Пример маски: +7 (999) 123-45-67
const d = digits.slice(0, 11); // ограничим 11 цифрами (под РФ)
if (!d.length) return '';
// если начали вводить с 8/7 — приведём к +7
let rest = d;
let prefix = '+7';
if (d[0] === '8') rest = '7' + d.slice(1);
if (rest[0] === '7') rest = rest.slice(1);
else {
// если первая цифра не 7, считаем что пользователь вводит локально, но всё равно показываем +7
// (при желании можно тут менять логику)
}
const a = rest.slice(0, 3);
const b = rest.slice(3, 6);
const c = rest.slice(6, 8);
const e = rest.slice(8, 10);
let out = prefix;
if (a) out += ` (${a}`;
if (a && a.length === 3) out += `)`;
if (b) out += ` ${b}`;
if (c) out += `-${c}`;
if (e) out += `-${e}`;
return out;
};
const applyToInput = (input) => {
if (input.dataset.phoneMasked === '1') return;
input.dataset.phoneMasked = '1';
// Подсказки браузеру: tel-поле и события input/change поддерживаются стандартно [web:2]
input.autocomplete = input.autocomplete || 'tel';
input.inputMode = input.inputMode || 'tel'; // подсказка клавиатуры, особенно на мобилках [web:12]
const handler = () => {
const digits = input.value.replace(/\D/g, '');
input.value = format(digits);
};
input.addEventListener('input', handler); // события input/change для tel описаны в MDN [web:2]
input.addEventListener('change', handler); // [web:2]
};
// 1) применяем ко всем уже существующим
document.querySelectorAll(selector).forEach(applyToInput);
// 2) применяем к добавляемым динамически
const mo = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (!(node instanceof Element)) continue;
if (node.matches?.(selector)) applyToInput(node);
node.querySelectorAll?.(selector).forEach(applyToInput);
}
}
});
mo.observe(document.documentElement, { childList: true, subtree: true });
return () => mo.disconnect();
}
// запуск
initPhoneMask();
function runOnDomUpdate(fn, root = document.documentElement) {
const observer = new MutationObserver((mutations) => {
// если важно “не дергать” лишний раз — фильтруем
if (mutations.some(m => m.type === 'childList' && (m.addedNodes.length || m.removedNodes.length))) {
fn();
}
});
observer.observe(root, { childList: true, subtree: true }); // childList/subtree — ключевые опции [web:16]
return () => observer.disconnect();
}
// пример: заново применить маску к новым инпутам
const stop = runOnDomUpdate(() => initPhoneMask());

View File

@ -59,10 +59,12 @@ class="bi bi-caret-left" viewBox="0 0 16 16">
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
<a id="navbarDropdown" class="nav-link dropdown-toggle link-secondary" href="#" role="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
<img src="../../images/icons/user.png" class="img-fluid align-middle"
style="height: 40px;">
<svg class="" xmlns="http://www.w3.org/2000/svg" width="38" height="38" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
</svg>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
@ -119,7 +121,16 @@ class="bi bi-caret-left" viewBox="0 0 16 16">
</div>
@endif
@foreach ($errors->all() as $error)
<div class="alert alert-danger d-flex align-items-center" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle" viewBox="0 0 16 16">
<path d="M7.938 2.016A.13.13 0 0 1 8.002 2a.13.13 0 0 1 .063.016.15.15 0 0 1 .054.057l6.857 11.667c.036.06.035.124.002.183a.2.2 0 0 1-.054.06.1.1 0 0 1-.066.017H1.146a.1.1 0 0 1-.066-.017.2.2 0 0 1-.054-.06.18.18 0 0 1 .002-.183L7.884 2.073a.15.15 0 0 1 .054-.057m1.044-.45a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767z"/>
<path d="M7.002 12a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 5.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
</svg>
<div class="ms-3 fw-bold">
{{ $error }}
</div>
</div>
@endforeach
@yield('content')
</div>
@ -129,5 +140,5 @@ class="bi bi-caret-left" viewBox="0 0 16 16">
</main>
</div>
</body>
@vite(['resources/js/phone-format.js'])
</html>

View File

@ -233,33 +233,7 @@ class="bi bi-emoji-astonished" viewBox="0 0 16 16">
</div>
@script
<script>
eventCalllback = function(e) {
var el = e.target,
clearVal = el.dataset.phoneClear,
pattern = el.dataset.phonePattern,
matrix_def = "+7(___) ___-__-__",
matrix = pattern ? pattern : matrix_def,
i = 0,
def = matrix.replace(/\D/g, ""),
val = e.target.value.replace(/\D/g, "");
if (clearVal !== 'false' && e.type === 'blur') {
if (val.length < matrix.match(/([\_\d])/g).length) {
e.target.value = '';
return;
}
}
if (def.length >= val.length) val = def;
e.target.value = matrix.replace(/./g, function(a) {
return /[_\d]/.test(a) && i < val.length ? val.charAt(i++) : i >= val.length ? "" :
a
});
}
var phone_inputs = document.querySelectorAll('[data-phone-pattern]');
for (let elem of phone_inputs) {
for (let ev of ['input', 'blur', 'focus']) {
elem.addEventListener(ev, eventCalllback);
}
}
</script>
@endscript
</div>

View File

@ -11,6 +11,7 @@ export default defineConfig({
'resources/css/app.css',
'resources/css/docs.css',
'resources/css/multiselect.css',
'resources/js/phone-format.js',
],
refresh: true,
}),