v1.0.0
This commit is contained in:
34
src/resources/views/components/custom-field-input.blade.php
Normal file
34
src/resources/views/components/custom-field-input.blade.php
Normal file
@@ -0,0 +1,34 @@
|
||||
@props(['field', 'wireModel'])
|
||||
|
||||
<div class="field">
|
||||
<label>{{ $field->label }}{{ $field->required ? ' *' : '' }}</label>
|
||||
|
||||
@switch($field->type)
|
||||
@case('text')
|
||||
<input class="input" wire:model="{{ $wireModel }}.{{ $field->id }}">
|
||||
@break
|
||||
@case('textarea')
|
||||
<textarea class="input" wire:model="{{ $wireModel }}.{{ $field->id }}"></textarea>
|
||||
@break
|
||||
@case('number')
|
||||
<input class="input" type="number" wire:model="{{ $wireModel }}.{{ $field->id }}">
|
||||
@break
|
||||
@case('date')
|
||||
<input class="input" type="date" wire:model="{{ $wireModel }}.{{ $field->id }}">
|
||||
@break
|
||||
@case('select')
|
||||
<select class="input" wire:model="{{ $wireModel }}.{{ $field->id }}">
|
||||
<option value="">Wybierz</option>
|
||||
@foreach ($field->options ?? [] as $option)
|
||||
<option value="{{ $option }}">{{ $option }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@break
|
||||
@case('checkbox')
|
||||
<label class="radio">
|
||||
<input type="checkbox" wire:model="{{ $wireModel }}.{{ $field->id }}" style="position:static;opacity:1;width:auto;height:auto">
|
||||
Tak
|
||||
</label>
|
||||
@break
|
||||
@endswitch
|
||||
</div>
|
||||
17
src/resources/views/components/login-notice.blade.php
Normal file
17
src/resources/views/components/login-notice.blade.php
Normal file
@@ -0,0 +1,17 @@
|
||||
@props(['html' => '', 'type' => 'info'])
|
||||
|
||||
{{--
|
||||
"ql-editor" is included alongside our own classes purely so Quill's own
|
||||
CSS (quill.snow.css, loaded in layouts/app.blade.php) applies here too —
|
||||
it's what makes alignment, indent, headings, blockquote and code-block
|
||||
formatting saved from the editor actually render outside of it (those
|
||||
are plain classes/tags in the stored HTML, and several of Quill's rules
|
||||
for them are scoped under .ql-editor). See app.css for the min-height /
|
||||
overflow reset that undoes the parts of that scoping meant for the live,
|
||||
editable textarea rather than static output like this.
|
||||
--}}
|
||||
@if (trim(strip_tags($html)) !== '')
|
||||
<div {{ $attributes->merge(['class' => 'login-notice login-notice-'.$type.' ql-editor']) }}>
|
||||
{!! $html !!}
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,9 @@
|
||||
@props(['attachment'])
|
||||
|
||||
<a
|
||||
href="{{ \Illuminate\Support\Facades\Storage::disk('public')->url($attachment->path) }}"
|
||||
target="_blank"
|
||||
style="display:inline-flex;align-items:center;gap:6px;margin-top:8px;padding:5px 10px;border:1px solid var(--color-divider);border-radius:6px;font-size:12.5px;color:inherit;text-decoration:none;background:color-mix(in srgb, var(--color-text) 5%, transparent)"
|
||||
>
|
||||
<span class="material-symbols-outlined" style="font-size:15px">attach_file</span>{{ $attachment->original_name }}
|
||||
</a>
|
||||
40
src/resources/views/components/multiselect.blade.php
Normal file
40
src/resources/views/components/multiselect.blade.php
Normal file
@@ -0,0 +1,40 @@
|
||||
@props(['options', 'selectedIds', 'toggleAction', 'placeholder' => 'Wybierz…'])
|
||||
|
||||
@php
|
||||
$selected = collect($options)->filter(fn ($o) => in_array($o['id'], $selectedIds, true))->values();
|
||||
$available = collect($options)->filter(fn ($o) => ! in_array($o['id'], $selectedIds, true))->values();
|
||||
@endphp
|
||||
|
||||
<div x-data="{ open: false }" style="position:relative">
|
||||
<div
|
||||
@click="open = ! open"
|
||||
style="display:flex;flex-wrap:wrap;gap:6px;align-items:center;border:1px solid var(--color-divider);border-radius:7px;padding:7px 8px;min-height:38px;cursor:pointer;background:var(--color-bg)"
|
||||
>
|
||||
@forelse ($selected as $opt)
|
||||
<span class="tag tag-accent" style="display:inline-flex;align-items:center;gap:5px">
|
||||
{{ $opt['label'] }}
|
||||
<span
|
||||
class="material-symbols-outlined"
|
||||
style="font-size:14px;cursor:pointer"
|
||||
wire:click.stop="{{ $toggleAction }}({{ $opt['id'] }})"
|
||||
>close</span>
|
||||
</span>
|
||||
@empty
|
||||
<span class="text-muted" style="font-size:13px">{{ $placeholder }}</span>
|
||||
@endforelse
|
||||
<span class="material-symbols-outlined" style="font-size:18px;margin-left:auto;opacity:0.6">expand_more</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="open"
|
||||
x-cloak
|
||||
@click.outside="open = false"
|
||||
style="position:absolute;top:100%;left:0;right:0;margin-top:4px;background:var(--color-surface);border:1px solid var(--color-divider);border-radius:8px;box-shadow:var(--shadow-md);max-height:220px;overflow:auto;z-index:30"
|
||||
>
|
||||
@forelse ($available as $opt)
|
||||
<button type="button" class="theme-toggle-option" wire:click="{{ $toggleAction }}({{ $opt['id'] }})">{{ $opt['label'] }}</button>
|
||||
@empty
|
||||
<div class="text-muted" style="padding:8px 12px;font-size:12.5px">Brak więcej opcji do dodania.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
53
src/resources/views/components/profile-menu.blade.php
Normal file
53
src/resources/views/components/profile-menu.blade.php
Normal file
@@ -0,0 +1,53 @@
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$areas = [];
|
||||
|
||||
if ($user) {
|
||||
if ($user->isClient()) {
|
||||
$areas[] = ['label' => 'Panel Klienta', 'url' => route('client.dashboard'), 'active' => request()->routeIs('client.*')];
|
||||
}
|
||||
if ($user->isOperator()) {
|
||||
$areas[] = ['label' => 'Panel Operatora', 'url' => route('operator.queue'), 'active' => request()->routeIs('operator.*')];
|
||||
}
|
||||
if ($user->isAdmin()) {
|
||||
$areas[] = ['label' => 'Panel Administratora', 'url' => route('admin.panel'), 'active' => request()->routeIs('admin.*')];
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if ($user)
|
||||
<div x-data="{ open: false }" @click.outside="open = false" style="position:relative;display:inline-block">
|
||||
<button type="button" class="btn btn-secondary" @click="open = !open" style="display:flex;align-items:center;gap:6px">
|
||||
<span class="material-symbols-outlined" style="font-size:18px">account_circle</span>
|
||||
<span class="profile-menu-name">{{ $user->name }}</span>
|
||||
<span class="material-symbols-outlined" style="font-size:16px">expand_more</span>
|
||||
</button>
|
||||
<div
|
||||
x-show="open"
|
||||
x-cloak
|
||||
style="position:absolute;top:100%;right:0;margin-top:6px;background:var(--color-surface);border:1px solid var(--color-divider);border-radius:8px;box-shadow:var(--shadow-md);min-width:200px;overflow:hidden;z-index:30"
|
||||
>
|
||||
@foreach ($areas as $area)
|
||||
<a
|
||||
href="{{ $area['url'] }}"
|
||||
wire:navigate
|
||||
@click="open = false"
|
||||
class="theme-toggle-option"
|
||||
style="text-decoration:none;color:{{ $area['active'] ? 'var(--color-accent)' : 'var(--color-text)' }};font-size:12.5px"
|
||||
>{{ $area['label'] }}</a>
|
||||
@endforeach
|
||||
|
||||
@if (count($areas))
|
||||
<div style="border-top:1px solid var(--color-divider)"></div>
|
||||
@endif
|
||||
|
||||
<a
|
||||
href="{{ route('logout') }}"
|
||||
onclick="event.preventDefault(); document.getElementById('profile-menu-logout-form').submit();"
|
||||
class="theme-toggle-option"
|
||||
style="text-decoration:none;color:var(--color-danger);font-size:12.5px"
|
||||
>Wyloguj się</a>
|
||||
<form id="profile-menu-logout-form" action="{{ route('logout') }}" method="POST" style="display:none">@csrf</form>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
74
src/resources/views/components/quill-editor.blade.php
Normal file
74
src/resources/views/components/quill-editor.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
@props(['value' => '', 'onChange', 'minHeight' => '160px'])
|
||||
|
||||
{{--
|
||||
A QuillJS rich-text editor whose HTML is pushed back to the Livewire
|
||||
component via the given method name, debounced so typing doesn't fire a
|
||||
request per keystroke. wire:ignore keeps Livewire from touching Quill's
|
||||
own DOM once it's mounted — either the wrapping @if in the parent view
|
||||
(opening a dialog) or a changing wire:key passed in via $attributes (e.g.
|
||||
after a server-side "Resetuj" reset) is what gives us a fresh node — and
|
||||
therefore a fresh x-init reading the new $value — when the content changes
|
||||
from outside the editor itself.
|
||||
--}}
|
||||
<div
|
||||
{{ $attributes }}
|
||||
wire:ignore
|
||||
x-data="{
|
||||
quill: null,
|
||||
timer: null,
|
||||
init() {
|
||||
// Quill has no built-in horizontal-rule button — register a
|
||||
// custom block-embed format for it once (guarded since every
|
||||
// editor instance on the page runs this same init). Community
|
||||
// pattern: https://github.com/quilljs/quill/issues/1211
|
||||
if (! window.quillDividerRegistered) {
|
||||
const BlockEmbed = Quill.import('blots/block/embed');
|
||||
class DividerBlot extends BlockEmbed {}
|
||||
DividerBlot.blotName = 'divider';
|
||||
DividerBlot.tagName = 'hr';
|
||||
Quill.register(DividerBlot);
|
||||
Quill.import('ui/icons').divider = `<svg viewBox='0 0 18 18'><line class='ql-stroke' x1='3' y1='9' x2='15' y2='9'></line></svg>`;
|
||||
window.quillDividerRegistered = true;
|
||||
}
|
||||
|
||||
this.quill = new Quill(this.$refs.editor, {
|
||||
theme: 'snow',
|
||||
placeholder: 'Treść…',
|
||||
modules: {
|
||||
toolbar: {
|
||||
container: [
|
||||
[{ header: [2, 3, false] }],
|
||||
['bold', 'italic', 'underline', 'strike'],
|
||||
[{ color: [] }, { background: [] }],
|
||||
[{ align: [] }],
|
||||
[{ list: 'ordered' }, { list: 'bullet' }],
|
||||
[{ indent: '-1' }, { indent: '+1' }],
|
||||
[{ script: 'sub' }, { script: 'super' }],
|
||||
['blockquote', 'code-block'],
|
||||
['divider'],
|
||||
['link'],
|
||||
['clean'],
|
||||
],
|
||||
handlers: {
|
||||
divider() {
|
||||
const range = this.quill.getSelection(true);
|
||||
this.quill.insertText(range.index, '\n', Quill.sources.USER);
|
||||
this.quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER);
|
||||
this.quill.setSelection(range.index + 2, 0, Quill.sources.SILENT);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
this.quill.root.innerHTML = @js($value);
|
||||
this.quill.on('text-change', () => {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => {
|
||||
$wire.call(@js($onChange), this.quill.root.innerHTML);
|
||||
}, 500);
|
||||
});
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div x-ref="editor" style="min-height: {{ $minHeight }}"></div>
|
||||
</div>
|
||||
35
src/resources/views/components/theme-toggle.blade.php
Normal file
35
src/resources/views/components/theme-toggle.blade.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<div
|
||||
x-data="{
|
||||
theme: localStorage.getItem('theme') || 'auto',
|
||||
open: false,
|
||||
icon() { return this.theme === 'light' ? 'light_mode' : this.theme === 'dark' ? 'dark_mode' : 'brightness_auto' },
|
||||
apply(t) {
|
||||
this.theme = t
|
||||
localStorage.setItem('theme', t)
|
||||
let systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
document.documentElement.setAttribute('data-theme', t === 'auto' ? (systemDark ? 'dark' : 'light') : t)
|
||||
this.open = false
|
||||
}
|
||||
}"
|
||||
@click.outside="open = false"
|
||||
style="position:relative;display:inline-block"
|
||||
>
|
||||
<button type="button" class="btn btn-secondary btn-icon" @click="open = !open">
|
||||
<span class="material-symbols-outlined" x-text="icon()"></span>
|
||||
</button>
|
||||
<div
|
||||
x-show="open"
|
||||
x-cloak
|
||||
style="position:absolute;top:100%;right:0;margin-top:6px;background:var(--color-surface);border:1px solid var(--color-divider);border-radius:8px;box-shadow:var(--shadow-md);min-width:150px;overflow:hidden;z-index:20"
|
||||
>
|
||||
<button type="button" class="theme-toggle-option" @click="apply('light')">
|
||||
<span class="material-symbols-outlined" style="font-size:16px">light_mode</span>Jasny
|
||||
</button>
|
||||
<button type="button" class="theme-toggle-option" @click="apply('dark')">
|
||||
<span class="material-symbols-outlined" style="font-size:16px">dark_mode</span>Ciemny
|
||||
</button>
|
||||
<button type="button" class="theme-toggle-option" @click="apply('auto')">
|
||||
<span class="material-symbols-outlined" style="font-size:16px">brightness_auto</span>Auto
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
22
src/resources/views/components/topbar.blade.php
Normal file
22
src/resources/views/components/topbar.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
@php
|
||||
$panelLabel = match (true) {
|
||||
request()->routeIs('client.*') => 'Panel Klienta',
|
||||
request()->routeIs('operator.*') => 'Panel Operatora',
|
||||
request()->routeIs('admin.*') => 'Panel Administratora',
|
||||
default => null,
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="nav" style="position:relative;padding:16px 28px;border-bottom:1px solid var(--color-divider)">
|
||||
<span class="nav-brand">{{ \App\Support\Settings::get('company_name') }}</span>
|
||||
|
||||
@if ($panelLabel)
|
||||
<span class="nav-panel-label" style="position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);font-weight:500;font-size:13.5px;white-space:nowrap">{{ $panelLabel }}</span>
|
||||
@endif
|
||||
|
||||
<x-theme-toggle />
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
<x-profile-menu />
|
||||
</div>
|
||||
Reference in New Issue
Block a user