bookCases added

This commit is contained in:
Benjamin Takats
2022-12-06 23:56:33 +01:00
parent caac426701
commit 35ae1c2a58
12 changed files with 552 additions and 0 deletions

View File

@@ -1,3 +1,8 @@
created:
- database/factories/BookCaseFactory.php
- database/migrations/2022_12_06_222651_create_book_cases_table.php
- app/Models/BookCase.php
- app/Nova/BookCase.php
models: models:
Category: { name: string, slug: string } Category: { name: string, slug: string }
City: { country_id: biginteger, name: string, slug: string, longitude: 'float:10', latitude: 'float:10' } City: { country_id: biginteger, name: string, slug: string, longitude: 'float:10', latitude: 'float:10' }
@@ -18,3 +23,5 @@ models:
TeamInvitation: { team_id: biginteger, email: string, role: 'string nullable' } TeamInvitation: { team_id: biginteger, email: string, role: 'string nullable' }
User: { name: string, public_key: 'string nullable', email: 'string nullable', email_verified_at: 'datetime nullable', password: 'string nullable', remember_token: 'string:100 nullable', current_team_id: 'biginteger nullable', profile_photo_path: 'string:2048 nullable', is_lecturer: 'boolean default:', two_factor_secret: 'text nullable', two_factor_recovery_codes: 'text nullable', two_factor_confirmed_at: 'datetime nullable', timezone: 'string default:Europe/Berlin' } User: { name: string, public_key: 'string nullable', email: 'string nullable', email_verified_at: 'datetime nullable', password: 'string nullable', remember_token: 'string:100 nullable', current_team_id: 'biginteger nullable', profile_photo_path: 'string:2048 nullable', is_lecturer: 'boolean default:', two_factor_secret: 'text nullable', two_factor_recovery_codes: 'text nullable', two_factor_confirmed_at: 'datetime nullable', timezone: 'string default:Europe/Berlin' }
Venue: { city_id: biginteger, name: string, slug: string, street: string } Venue: { city_id: biginteger, name: string, slug: string, street: string }
Case: { title: string, lat: double, lon: json, address: text, type: string, open: string, comment: text, contact: text, bcz: text, digital: boolean, icontype: string, deactivated: boolean, deactreason: string, entrytype: string, homepage: string }
BookCase: { title: string, lat: double, lon: json, address: text, type: string, open: string, comment: text, contact: text, bcz: text, digital: boolean, icontype: string, deactivated: boolean, deactreason: string, entrytype: string, homepage: string }

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Console\Commands\OpenBooks;
use App\Models\BookCase;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;
class SyncOpenBooks extends Command
{
/**
* The name and signature of the console command.
* @var string
*/
protected $signature = 'books:sync';
/**
* The console command description.
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
* @return int
*/
public function handle()
{
$response = Http::post('https://openbookcase.de/api/listarea/83.08995477111446/-200.56640625000003/-38.13455657705413/221.30859375000003');
foreach ($response->json()['cases'] as $case) {
BookCase::updateOrCreate(
[
'id' => $case['id'],
],
[
'title' => $case['title'],
'lat' => (float)$case['lat'],
'lon' => (float)$case['lon'],
'address' => $case['address'],
'type' => $case['type'],
'open' => $case['open'],
'comment' => $case['comment'],
'contact' => $case['contact'],
'bcz' => $case['bcz'],
'digital' => $case['digital'] ?? false,
'icontype' => $case['icontype'],
'deactivated' => $case['deactivated'],
'deactreason' => $case['deactreason'],
'entrytype' => $case['entrytype'],
'homepage' => $case['homepage'],
]
);
}
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Livewire\Frontend;
use App\Models\BookCase;
use App\Models\Country;
use Livewire\Component;
class SearchBookCase extends Component
{
public string $c = 'de';
public function render()
{
return view('livewire.frontend.search-book-case', [
'bookCases' => BookCase::get(),
'countries' => Country::query()
->select(['code', 'name'])
->get(),
]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Http\Livewire\Tables;
use App\Models\BookCase;
use Rappasoft\LaravelLivewireTables\DataTableComponent;
use Rappasoft\LaravelLivewireTables\Views\Column;
use Rappasoft\LaravelLivewireTables\Views\Columns\BooleanColumn;
class BookCaseTable extends DataTableComponent
{
protected $model = BookCase::class;
public function configure(): void
{
$this->setPrimaryKey('id')
->setAdditionalSelects(['id'])
->setThAttributes(function (Column $column) {
return [
'class' => 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider dark:bg-gray-800 dark:text-gray-400',
'default' => false,
];
})
->setTdAttributes(function (Column $column, $row, $columnIndex, $rowIndex) {
return [
'class' => 'px-6 py-4 text-sm font-medium dark:text-white',
'default' => false,
];
})
->setColumnSelectStatus(false)
->setPerPage(50);
}
public function columns(): array
{
return [
Column::make("Name", "title")
->sortable()
->searchable(),
Column::make("Adresse", "address")
->sortable()
->searchable(),
Column::make("Link")
->label(
fn(
$row,
Column $column
) => '<a class="underline text-amber-500" href="'.$row->homepage.'">Link</a>'
)
->html(),
BooleanColumn::make('Oranged-Pilled', 'deactivated')
->sortable(),
];
}
}

31
app/Models/BookCase.php Normal file
View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class BookCase extends Model
{
use HasFactory;
/**
* The attributes that aren't mass assignable.
*
* @var array
*/
protected $guarded = [];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'lat' => 'double',
'lon' => 'array',
'digital' => 'boolean',
'deactivated' => 'boolean',
];
}

141
app/Nova/BookCase.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
namespace App\Nova;
use Laravel\Nova\Fields\ID;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\Code;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Number;
use Laravel\Nova\Fields\Boolean;
class BookCase extends Resource
{
/**
* The model the resource corresponds to.
*
* @var string
*/
public static $model = \App\Models\BookCase::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'id';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'id',
];
/**
* Get the fields displayed by the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')
->rules('required', 'string'),
Number::make('Lat')
->rules('required', 'numeric'),
Code::make('Lon')
->rules('required', 'json')
->json(),
Text::make('Address')
->rules('required', 'string'),
Text::make('Type')
->rules('required', 'string'),
Text::make('Open')
->rules('required', 'string'),
Text::make('Comment')
->rules('required', 'string'),
Text::make('Contact')
->rules('required', 'string'),
Text::make('Bcz')
->rules('required', 'string'),
Boolean::make('Digital')
->rules('required'),
Text::make('Icontype')
->rules('required', 'string'),
Boolean::make('Deactivated')
->rules('required'),
Text::make('Deactreason')
->rules('required', 'string'),
Text::make('Entrytype')
->rules('required', 'string'),
Text::make('Homepage')
->rules('required', 'string'),
];
}
/**
* Get the cards available for the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function cards(Request $request)
{
return [];
}
/**
* Get the filters available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function filters(Request $request)
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function lenses(Request $request)
{
return [];
}
/**
* Get the actions available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function actions(Request $request)
{
return [];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use App\Models\BookCase;
class BookCaseFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = BookCase::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition(): array
{
return [
'title' => $this->faker->sentence(4),
'lat' => $this->faker->latitude,
'lon' => '{}',
'address' => $this->faker->text,
'type' => $this->faker->word,
'open' => $this->faker->word,
'comment' => $this->faker->text,
'contact' => $this->faker->text,
'bcz' => $this->faker->text,
'digital' => $this->faker->boolean,
'icontype' => $this->faker->word,
'deactivated' => $this->faker->boolean,
'deactreason' => $this->faker->word,
'entrytype' => $this->faker->word,
'homepage' => $this->faker->word,
];
}
}

View File

@@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateBookCasesTable extends Migration
{
/**
* Run the migrations.
* @return void
*/
public function up(): void
{
Schema::create('book_cases', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->double('lat');
$table->double('lon');
$table->text('address')
->nullable();
$table->string('type');
$table->string('open')
->nullable();
$table->text('comment')
->nullable();
$table->text('contact')
->nullable();
$table->text('bcz')
->nullable();
$table->boolean('digital');
$table->string('icontype');
$table->boolean('deactivated');
$table->string('deactreason');
$table->string('entrytype');
$table->text('homepage')
->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
* @return void
*/
public function down(): void
{
Schema::dropIfExists('book_cases');
}
}

View File

@@ -5,6 +5,7 @@ namespace Database\Seeders;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents; // use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use App\Console\Commands\Database\CreateTags; use App\Console\Commands\Database\CreateTags;
use App\Console\Commands\Feed\ReadAndSyncEinundzwanzigPodcastFeed; use App\Console\Commands\Feed\ReadAndSyncEinundzwanzigPodcastFeed;
use App\Console\Commands\OpenBooks\SyncOpenBooks;
use App\Models\Category; use App\Models\Category;
use App\Models\City; use App\Models\City;
use App\Models\Country; use App\Models\Country;
@@ -279,5 +280,6 @@ class DatabaseSeeder extends Seeder
$nonPublicLibrary->libraryItems() $nonPublicLibrary->libraryItems()
->attach($libraryItem); ->attach($libraryItem);
Artisan::call(ReadAndSyncEinundzwanzigPodcastFeed::class); Artisan::call(ReadAndSyncEinundzwanzigPodcastFeed::class);
Artisan::call(SyncOpenBooks::class);
} }
} }

View File

@@ -0,0 +1,17 @@
models:
BookCase:
title: string
lat: double
lon: json
address: text
type: string
open: string
comment: text
contact: text
bcz: text
digital: boolean
icontype: string
deactivated: boolean
deactreason: string
entrytype: string
homepage: string

View File

@@ -0,0 +1,123 @@
<div class="bg-21gray flex flex-col h-screen justify-between">
<script src="{{ asset('earth/miniature.earth.js') }}"></script>
<style>
.earth-container::after {
content: "";
position: absolute;
height: 22%;
bottom: 4%;
left: 13%;
right: 13%;
}
</style>
{{-- HEADER --}}
<div>
<section class="w-full">
<div class="max-w-screen-2xl mx-auto px-2 sm:px-10">
<div
class="relative sm:sticky sm:top-0 bg-21gray z-10 flex flex-col flex-wrap items-center justify-between py-7 mx-auto md:flex-row max-w-screen-2xl">
<div class="relative flex flex-col md:flex-row">
<a href="{{ route('search.city', ['country' => $c]) }}"
class="flex items-center mb-5 font-medium text-gray-900 lg:w-auto lg:items-center lg:justify-center md:mb-0">
<img src="{{ asset('img/einundzwanzig-horizontal-inverted.svg') }}">
</a>
<nav
class="flex flex-wrap items-center mb-5 text-lg md:mb-0 md:pl-8 md:ml-8 md:border-l md:border-gray-800">
<a href="{{ route('search.city', ['country' => $c, '#table']) }}"
class="{{ request()->routeIs('search.city') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Städte</a>
<a href="{{ route('search.lecturer', ['country' => $c, '#table']) }}"
class="{{ request()->routeIs('search.lecturer') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Dozenten</a>
<a href="{{ route('search.venue', ['country' => $c, '#table']) }}"
class="{{ request()->routeIs('search.venue') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Veranstaltungs-Orte</a>
<a href="{{ route('search.course', ['country' => $c, '#table']) }}"
class="{{ request()->routeIs('search.course') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Kurse</a>
<a href="{{ route('search.event', ['country' => $c, '#table']) }}"
class="{{ request()->routeIs('search.event') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Termine</a>
<a href="{{ route('library', ['country' => $c]) }}"
class="{{ request()->routeIs('library') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Bibliothek</a>
@if(auth()->user()?->is_lecturer)
<a href="{{ route('library.lecturer', ['country' => $c]) }}"
class="{{ request()->routeIs('library.lecturer') ? 'text-amber-500 underline' : 'text-gray-400' }} mr-5 font-medium leading-6 hover:text-gray-300">Dozenten-Bibliothek</a>
@endif
</nav>
</div>
@auth
<div></div>
@else
<div class="inline-flex items-center ml-5 my-2 text-lg space-x-6 lg:justify-end">
<a href="{{ route('auth.ln') }}"
class="text-xs sm:text-base font-medium leading-6 text-gray-400 hover:text-gray-300 whitespace-no-wrap transition duration-150 ease-in-out">
Login
</a>
<a href="{{ route('auth.ln') }}"
class="text-xs sm:text-base inline-flex items-center justify-center px-4 py-2 font-medium leading-6 text-gray-200 hover:text-white whitespace-no-wrap bg-gray-800 border border-transparent rounded shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-800">
Registrieren
</a>
</div>
@endauth
</div>
<div class="flex lg:flex-row flex-col pt-4 md:pt-4 lg:pt-4">
<div
class="w-full lg:w-1/2 flex lg:px-0 px-5 flex-col md:items-center lg:items-start justify-center -mt-12">
<h1 class="text-white text-3xl sm:text-5xl lg:max-w-none max-w-4xl lg:text-left text-left md:text-center xl:text-7xl font-black">
Bitcoin <span
class="bg-clip-text text-transparent bg-gradient-to-br from-yellow-400 via-yellow-500 to-yellow-700 mt-1 lg:block">School
</span> <br class="lg:block sm:hidden"> Worldwide
</h1>
<p class="text-gray-500 sm:text-lg md:text-xl xl:text-2xl lg:max-w-none max-w-2xl md:text-center lg:text-left lg:pr-32 mt-6">
Finde einen Bücher-Schrank in deiner City</p>
<a href="#table"
class="whitespace-nowrap bg-white px-4 lg:px-16 py-2 text-center lg:py-5 font-bold rounded text-xs md:text-xl lg:text-2xl mt-8 inline-block w-auto">
👇 Bücher-Schrank finden 👇
</a>
<p class="text-gray-400 font-normal mt-4">{{-- TEXT --}}</p>
</div>
<div
x-data="{
earth: null,
init() {
this.earth = new Earth(this.$refs.myearth, {
location : {lat: {{ $bookCases->first()->lat }}, lng: {{ $bookCases->first()->lon }}},
zoom: 2,
light: 'sun',
polarLimit: 0.6,
transparent : true,
mapSeaColor : 'RGBA(34, 34, 34,0.76)',
mapLandColor : '#F7931A',
mapBorderColor : '#5D5D5D',
mapBorderWidth : 0.25,
mapHitTest : true,
autoRotate: true,
autoRotateSpeed: 0.7,
autoRotateDelay: 500,
});
this.earth.addEventListener('ready', function() {
@foreach($bookCases as $city)
this.addMarker( {
mesh : ['Needle'],
location : { lat: {{ $city->lat }}, lng: {{ $city->lon }} },
});
@endforeach
});
}
}" class="hidden sm:inline-block w-1/2">
{{--<img src="https://cdn.devdojo.com/images/march2022/mesh-gradient1.png"
class="absolute lg:max-w-none max-w-3xl mx-auto mt-32 w-full h-full inset-0">--}}
<div x-ref="myearth" class="earth-container"></div>
</div>
</div>
</div>
</section>
</div>
{{-- MAIN --}}
<section class="w-full mb-12">
<div class="max-w-screen-2xl mx-auto px-2 sm:px-10" id="table">
<livewire:tables.book-case-table/>
</div>
</section>
{{-- FOOTER --}}
<livewire:frontend.footer/>
</div>

View File

@@ -39,6 +39,9 @@ Route::get('/{country:code}/dozenten/bibliothek', \App\Http\Livewire\Frontend\Li
Route::get('/dozenten', \App\Http\Livewire\Guest\Welcome::class) Route::get('/dozenten', \App\Http\Livewire\Guest\Welcome::class)
->name('search.lecturers'); ->name('search.lecturers');
Route::get('/buecher-schraenke', \App\Http\Livewire\Frontend\SearchBookCase::class)
->name('search.bookcases');
Route::middleware([ Route::middleware([
'auth:sanctum', 'auth:sanctum',
config('jetstream.auth_session'), config('jetstream.auth_session'),