diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6619d3d1a..a0d68397c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file[^1].
## [Unreleased]
+### Added
+- Featured Artworks back-end section
+- new Tailwind component for back-end/admin
+
### Changed
- font loading (fix for Source Serif Pro)
diff --git a/app/FeaturedArtwork.php b/app/FeaturedArtwork.php
new file mode 100644
index 000000000..3b3a4bdbf
--- /dev/null
+++ b/app/FeaturedArtwork.php
@@ -0,0 +1,77 @@
+belongsTo(Item::class);
+ }
+
+ public function getIsPublishedAttribute() {
+ return !!$this->published_at;
+ }
+
+ public function setIsPublishedAttribute(bool $isPublished) {
+ if ($this->is_published === $isPublished) return;
+ if (!$isPublished) return $this->attributes['published_at'] = null;
+
+ $this->attributes['published_at'] = Carbon::now();
+ }
+
+ public function scopePublished($query)
+ {
+ return $query->where('published_at', '<=', Carbon::now());
+ }
+
+ public function getAuthorLinksAttribute()
+ {
+ if (!$this->item) return;
+
+ return $this->item
+ ->authors_with_authorities
+ ->map(fn ($a) => (object) [
+ 'label' => formatName($a->name),
+ 'url' => isset($a->authority) ? $a->authority->getUrl() : route('frontend.catalog.index', ['author' => $a->name])
+ ]);
+ }
+
+ public function getMetadataLinksAttribute()
+ {
+ if (!$this->item) return;
+
+ $dating = (object) [
+ 'label' => $this->item->getDatingFormated(),
+ 'url' => null,
+ ];
+
+ $techniques = collect($this->item->techniques)
+ ->map(fn ($t) => (object) [
+ 'label' => $t,
+ 'url' => route('frontend.catalog.index', ['technique' => $t]),
+ ]);
+
+ $media = collect($this->item->mediums)
+ ->map(fn ($m) => (object) [
+ 'label' => $m,
+ 'url' => route('frontend.catalog.index', ['medium' => $m]),
+ ]);
+
+
+ return collect([$dating])
+ ->concat($techniques)
+ ->concat($media);
+ }
+}
diff --git a/app/Http/Controllers/Admin/FeaturedArtworkController.php b/app/Http/Controllers/Admin/FeaturedArtworkController.php
new file mode 100644
index 000000000..46fba9ee3
--- /dev/null
+++ b/app/Http/Controllers/Admin/FeaturedArtworkController.php
@@ -0,0 +1,91 @@
+ 'required',
+ 'description' => 'required',
+ 'item_id' => 'required'
+ ];
+
+ public function index()
+ {
+ return view('featured_artworks.index', [
+ 'artworks' => FeaturedArtwork::query()
+ ->with('item')
+ ->orderBy('id', 'desc')
+ ->get(),
+ 'lastPublishedId' => FeaturedArtwork::query()
+ ->published()
+ ->orderBy('published_at', 'desc')
+ ->value('id')
+ ]);
+ }
+
+ public function create(Request $request)
+ {
+ $item = $request->query('itemId') ? Item::find($request->query('itemId')) : null;
+
+ return view('featured_artworks.form', [
+ 'artwork' => new FeaturedArtwork([
+ 'title' => optional($item)->title,
+ 'description' => optional($item)->description,
+ 'item' => $item,
+ ])
+ ]);
+ }
+
+ public function edit(FeaturedArtwork $featuredArtwork)
+ {
+ return view('featured_artworks.form', [
+ 'artwork' => $featuredArtwork,
+ ]);
+ }
+
+ public function store(Request $request)
+ {
+ $request->validate(self::$rules);
+ $featuredArtwork = FeaturedArtwork::create(array_merge(
+ $request->input(),
+ [
+ 'is_published' => $request->boolean('is_published')
+ ]
+ ));
+
+ return redirect()
+ ->route('featured-artworks.index')
+ ->with('message', "Vybrané dielo \"{$featuredArtwork->title}\" bolo vytvorené");
+ }
+
+ public function update(Request $request, FeaturedArtwork $featuredArtwork)
+ {
+ $request->validate(self::$rules);
+
+ $featuredArtwork->update(array_merge(
+ $request->input(),
+ [
+ 'is_published' => $request->boolean('is_published')
+ ]
+ ));
+
+ return redirect()
+ ->route('featured-artworks.index')
+ ->with('message', "Vybrané dielo \"{$featuredArtwork->title}\" bolo upravené");
+ }
+
+ public function destroy(FeaturedArtwork $featuredArtwork)
+ {
+ $featuredArtwork->delete();
+
+ return redirect()
+ ->route('featured-artworks.index')
+ ->with('message', 'Odporúčané dielo bolo zmazané');
+ }
+}
diff --git a/app/Item.php b/app/Item.php
index fd335f744..b4e657242 100644
--- a/app/Item.php
+++ b/app/Item.php
@@ -351,6 +351,24 @@ public function getAuthorsWithoutAuthority()
->keys();
}
+ public function getAuthorsWithAuthoritiesAttribute()
+ {
+ $authorities = $this
+ ->authorities
+ ->map(fn ($authority) => (object) [
+ 'name' => $authority->name,
+ 'authority' => $authority
+ ]);
+
+ $authors = $this
+ ->getAuthorsWithoutAuthority()
+ ->map(fn ($author) => (object) [
+ 'name' => $author
+ ]);
+
+ return $authorities->concat($authors);
+ }
+
public function getFirstAuthorAttribute($value)
{
$authors_array = $this->makeArray($this->author);
diff --git a/app/View/Components/Admin/Form.php b/app/View/Components/Admin/Form.php
new file mode 100644
index 000000000..7511f5af4
--- /dev/null
+++ b/app/View/Components/Admin/Form.php
@@ -0,0 +1,59 @@
+model = $model;
+ $this->url = $this->buildUrl($url);
+ $this->method = $this->buildMethod($method);
+ }
+
+ private function buildUrl(?string $url): string {
+ if ($url) return $url;
+
+ // Try to guess url from model class
+ $routeName = Str::of(class_basename($this->model))->plural()->kebab();
+
+ if ($this->model->exists) {
+ return route("$routeName.update", [$this->model]);
+ }
+
+ return route("$routeName.store");
+ }
+
+ private function buildMethod(?string $method): string {
+ if ($method) return $method;
+
+ if ($this->model->exists) return 'patch';
+
+ return 'post';
+ }
+
+ /**
+ * Get the view / contents that represent the component.
+ *
+ * @return \Illuminate\Contracts\View\View|\Closure|string
+ */
+ public function render()
+ {
+ return view('components.admin.form');
+ }
+}
diff --git a/database/migrations/2022_02_18_065826_create_featured_artworks_table.php b/database/migrations/2022_02_18_065826_create_featured_artworks_table.php
new file mode 100644
index 000000000..4d96e926c
--- /dev/null
+++ b/database/migrations/2022_02_18_065826_create_featured_artworks_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->string('item_id');
+ $table->foreign('item_id')->references('id')->on('items');
+ $table->string('title');
+ $table->text('description');
+ $table->dateTime('published_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('featured_artworks');
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 6726be1f6..685cbee98 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2912,6 +2912,14 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true
},
+ "corejs-typeahead": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/corejs-typeahead/-/corejs-typeahead-1.3.1.tgz",
+ "integrity": "sha512-fyNlBNWJNL6EQUnJyAunEzBzRcwR2cEHtZXBi2pndHPOJ/wpOf3wbS+/Oh+kYYS5sKowQcs0LFwMSl6Y2Xeqkw==",
+ "requires": {
+ "jquery": ">=1.11"
+ }
+ },
"cosmiconfig": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
@@ -4688,7 +4696,6 @@
"minipass": {
"version": "2.9.0",
"bundled": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -4893,8 +4900,7 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -4984,8 +4990,7 @@
},
"yallist": {
"version": "3.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
}
}
},
@@ -6879,7 +6884,7 @@
},
"mkdirp": {
"version": "0.5.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
@@ -6889,7 +6894,7 @@
"dependencies": {
"minimist": {
"version": "0.0.8",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true,
"optional": true
@@ -8315,7 +8320,7 @@
"dependencies": {
"jsesc": {
"version": "0.5.0",
- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true
}
diff --git a/package.json b/package.json
index 4c4461933..6d5bff9dd 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"dependencies": {
"bootstrap": "^3.4.1",
"clipboard": "^2.0.6",
+ "corejs-typeahead": "^1.3.1",
"date-fns": "^2.25.0",
"debounce": "1.2.0",
"flickity": "^2.2.1",
diff --git a/resources/js/admin.js b/resources/js/admin.js
index 1fbc4b3a2..110ae1b98 100644
--- a/resources/js/admin.js
+++ b/resources/js/admin.js
@@ -1,7 +1,9 @@
window.debounce = require('debounce');
window.Vue = require('vue').default;
-Vue.component('linked-combos', require('./components/vue/linked-combos').default);
Vue.component('admin-links-input', require('./components/admin/LinksInput.vue').default);
+Vue.component('autocomplete', require('./components/Autocomplete.vue').default);
+Vue.component('linked-combos', require('./components/vue/linked-combos').default);
+Vue.component('query-string', require('./components/QueryString.vue').default);
new Vue({ el: '#wrapper' })
diff --git a/resources/js/components/Autocomplete.vue b/resources/js/components/Autocomplete.vue
new file mode 100644
index 000000000..61fecd240
--- /dev/null
+++ b/resources/js/components/Autocomplete.vue
@@ -0,0 +1,70 @@
+
+
Publikované {{ $artwork->published_at }}
+ @endif +ID | +Dielo | +Vytvorené | +Publikované | +Akcie | +
---|---|---|---|---|
{{ $a->id }} | +
+
+
+
+ {{ $a->title }}+{{ $a->item->author }} + |
+ + @datetime($a->created_at) + | +
+ @if ($a->is_published)
+
+ @datetime($a->published_at)
+
+ @if ($a->id == $lastPublishedId)
+ + Najnovšie + @endif + @endif + |
+
+
+
+ |
+