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) 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')); $company->update($request->only('name', 'email', 'phone'));
if ($request->token) { if ($request->token) {
$company->bitrixy->token = $request->token; $company->bitrixy->token = $request->token;

View File

@ -28,6 +28,7 @@ public function index(Request $request)
$complexes = $complexes->get(); $complexes = $complexes->get();
return view('admin::complexes.index', [ return view('admin::complexes.index', [
'complexes' => $complexes, 'complexes' => $complexes,
'cities' => City::orderBy('name')->get(),
'filter' => $request->filter 'filter' => $request->filter
]); ]);
} }
@ -48,7 +49,16 @@ public function update(Request $request, Complex $complex)
public function create(Request $request) 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'); return to_route('admin.complexes');
} }

View File

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

View File

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

View File

@ -47,8 +47,8 @@
</td> </td>
<td class="text-end"> <td class="text-end">
<div class="dropdown" style=""> <div class="dropdown" style="">
<button class="btn btn-light" type="button" id="dropdownMenuButton" <button class="btn btn-light" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">
@if ($complex->trashed()) @if ($complex->trashed())
<i class="bi bi-recycle"></i> <i class="bi bi-recycle"></i>
@else @else
@ -65,8 +65,7 @@
@else @else
<a class="dropdown-item" <a class="dropdown-item"
href="{{ route('admin.complexes.edit', ['complex' => $complex]) }}">Редактировать</a> href="{{ route('admin.complexes.edit', ['complex' => $complex]) }}">Редактировать</a>
<form method="post" <form method="post" action="{{ route('admin.complexes.delete', ['complex' => $complex]) }}">
action="{{ route('admin.complexes.delete', ['complex' => $complex]) }}">
@csrf @csrf
<button class="dropdown-item" type="submit">Удалить</button> <button class="dropdown-item" type="submit">Удалить</button>
</form> </form>
@ -83,21 +82,28 @@
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="createCityModal" tabindex="-1" aria-labelledby="createCityModalLabel" aria-hidden="true"> <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-content">
<div class="modal-header"> <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> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@csrf
<div class="my-3"> <div class="mb-3">
@csrf <label for="nameFormControl" class="form-label">Название</label>
<label for="cityNameInput" class="form-label">Введите название нового города</label> <input type="text" class="form-control" id="nameFormControl" name="name"
<input class="form-control" type="text" id="cityNameInput" name="name" required> value="{{ old('name') }}">
@error('text') </div>
<div class="text-danger">{{ $message }}</div> <div class="mb-3">
@enderror <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>
</div> </div>
@ -107,4 +113,4 @@
</div> </div>
</form> </form>
</div> </div>
@endsection @endsection

View File

@ -22,7 +22,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="phoneFormControl" class="form-label">телефон</label> <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 }}"> value="{{ $user->phone }}">
@error('phone') @error('phone')
<div class="text-danger">{{ $message }}</div> <div class="text-danger">{{ $message }}</div>

View File

@ -100,7 +100,7 @@
</div> </div>
<div class="my-3"> <div class="my-3">
<label for="nameInput" class="form-label">Телефон</label> <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') @error('phone')
<div class="text-danger">{{ $message }}</div> <div class="text-danger">{{ $message }}</div>
@enderror @enderror

View File

@ -46,7 +46,7 @@
<div class="mb-3"> <div class="mb-3">
<label for="agentPhone" class="form-label">Телефон</label> <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>
<div class="mb-3 form-check form-switch"> <div class="mb-3 form-check form-switch">

View File

@ -46,7 +46,7 @@
<div class="mb-3"> <div class="mb-3">
<label for="adminPhone" class="form-label">Телефон</label> <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>
<div class="mb-3 form-check form-switch"> <div class="mb-3 form-check form-switch">

View File

@ -24,7 +24,7 @@
if($json['total_commits'] > 0) 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>"; 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 --> <!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item dropdown"> <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> data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
<img src="../../images/icons/user.png" class="img-fluid align-middle" <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">
style="height: 40px;"> <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> </a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown"> <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> </div>
@endif @endif
@foreach ($errors->all() as $error) @foreach ($errors->all() as $error)
{{ $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 @endforeach
@yield('content') @yield('content')
</div> </div>
@ -129,5 +140,5 @@ class="bi bi-caret-left" viewBox="0 0 16 16">
</main> </main>
</div> </div>
</body> </body>
@vite(['resources/js/phone-format.js'])
</html> </html>

View File

@ -233,33 +233,7 @@ class="bi bi-emoji-astonished" viewBox="0 0 16 16">
</div> </div>
@script @script
<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> </script>
@endscript @endscript
</div> </div>

View File

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