Add geolocation for member map
continuous-integration/drone/push Build is failing Details
continuous-integration/drone/tag Build is failing Details

This commit is contained in:
Philipp Lang 2023-05-16 17:19:56 +02:00
parent 29b9959f09
commit 03d514d107
23 changed files with 1397 additions and 494 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@
/public/css /public/css
/public/fonts /public/fonts
/public/img /public/img
/public/images
/storage/*.key /storage/*.key
/vendor /vendor
.env .env

View File

@ -28,13 +28,17 @@ use Laravel\Scout\Searchable;
use Sabre\VObject\Component\VCard; use Sabre\VObject\Component\VCard;
use Sabre\VObject\Reader; use Sabre\VObject\Reader;
use Spatie\LaravelData\Lazy; use Spatie\LaravelData\Lazy;
use Zoomyboy\Osm\Address;
use Zoomyboy\Osm\Coordinate;
use Zoomyboy\Osm\Geolocatable;
use Zoomyboy\Osm\HasGeolocation;
use Zoomyboy\Phone\HasPhoneNumbers; use Zoomyboy\Phone\HasPhoneNumbers;
/** /**
* @property string $subscription_name * @property string $subscription_name
* @property int $pending_payment * @property int $pending_payment
*/ */
class Member extends Model class Member extends Model implements Geolocatable
{ {
use Notifiable; use Notifiable;
use HasNamiField; use HasNamiField;
@ -42,6 +46,7 @@ class Member extends Model
use Sluggable; use Sluggable;
use Searchable; use Searchable;
use HasPhoneNumbers; use HasPhoneNumbers;
use HasGeolocation;
/** /**
* @var array<string, string> * @var array<string, string>
@ -51,7 +56,7 @@ class Member extends Model
/** /**
* @var array<int, string> * @var array<int, string>
*/ */
public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug']; public static array $namiFields = ['firstname', 'lastname', 'joined_at', 'birthday', 'send_newspaper', 'address', 'zip', 'location', 'nickname', 'other_country', 'further_address', 'main_phone', 'mobile_phone', 'work_phone', 'fax', 'email', 'email_parents', 'gender_id', 'confession_id', 'region_id', 'country_id', 'fee_id', 'nationality_id', 'slug', 'lat', 'lon'];
/** /**
* @var array<int, string> * @var array<int, string>
@ -497,4 +502,24 @@ class Member extends Model
$this->fullAddress, $this->fullAddress,
])->implode(' '); ])->implode(' ');
} }
// -------------------------------- Geolocation --------------------------------
// *****************************************************************************
public function fillCoordinate(Coordinate $coordinate): void
{
$this->updateQuietly(['lat' => $coordinate->lat, 'lon' => $coordinate->lon]);
}
public function getAddressForGeolocation(): ?Address
{
return new Address($this->address, $this->zip, $this->location);
}
public function destroyCoordinate(): void
{
$this->updateQuietly([
'lat' => null,
'lon' => null,
]);
}
} }

View File

@ -102,6 +102,8 @@ class MemberResource extends JsonResource
'salutation' => $this->salutation, 'salutation' => $this->salutation,
'mitgliedsnr' => $this->mitgliedsnr, 'mitgliedsnr' => $this->mitgliedsnr,
'comment' => $this->comment, 'comment' => $this->comment,
'lat' => $this->lat,
'lon' => $this->lon,
'links' => [ 'links' => [
'show' => route('member.show', ['member' => $this->getModel()]), 'show' => route('member.show', ['member' => $this->getModel()]),
'edit' => route('member.edit', ['member' => $this->getModel()]), 'edit' => route('member.edit', ['member' => $this->getModel()]),

View File

@ -48,7 +48,8 @@
"worksome/request-factories": "^2.5", "worksome/request-factories": "^2.5",
"zoomyboy/laravel-nami": "dev-master", "zoomyboy/laravel-nami": "dev-master",
"zoomyboy/phone": "^1.0", "zoomyboy/phone": "^1.0",
"zoomyboy/tex": "dev-main as 1.0" "zoomyboy/tex": "dev-main as 1.0",
"zoomyboy/osm": "^1.0"
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.9.1", "fakerphp/faker": "^1.9.1",

1003
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('members', function (Blueprint $table) {
$table->double('lat')->nullable();
$table->double('lon')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('members', function (Blueprint $table) {
});
}
};

32
package-lock.json generated
View File

@ -8,6 +8,7 @@
"@inertiajs/inertia": "^0.11.0", "@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue": "^0.8.0", "@inertiajs/inertia-vue": "^0.8.0",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"leaflet": "^1.9.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merge": "^2.1.1", "merge": "^2.1.1",
"pinia": "^2.0.35", "pinia": "^2.0.35",
@ -16,6 +17,7 @@
"svg-sprite": "^2.0.2", "svg-sprite": "^2.0.2",
"v-tooltip": "^2.1.3", "v-tooltip": "^2.1.3",
"vue-toasted": "^1.1.28", "vue-toasted": "^1.1.28",
"vue2-leaflet": "^2.7.1",
"wnumb": "^1.2.0" "wnumb": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
@ -2397,6 +2399,12 @@
"@types/range-parser": "*" "@types/range-parser": "*"
} }
}, },
"node_modules/@types/geojson": {
"version": "7946.0.10",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
"peer": true
},
"node_modules/@types/glob": { "node_modules/@types/glob": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
@ -2468,6 +2476,15 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true "dev": true
}, },
"node_modules/@types/leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-Caa1lYOgKVqDkDZVWkto2Z5JtVo09spEaUt2S69LiugbBpoqQu92HYFMGUbYezZbnBkyOxMNPXHSgRrRY5UyIA==",
"peer": true,
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/mime": { "node_modules/@types/mime": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
@ -6479,6 +6496,11 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/leaflet": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -9979,6 +10001,16 @@
"resolved": "https://registry.npmjs.org/vue-toasted/-/vue-toasted-1.1.28.tgz", "resolved": "https://registry.npmjs.org/vue-toasted/-/vue-toasted-1.1.28.tgz",
"integrity": "sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw==" "integrity": "sha512-UUzr5LX51UbbiROSGZ49GOgSzFxaMHK6L00JV8fir/CYNJCpIIvNZ5YmS4Qc8Y2+Z/4VVYRpeQL2UO0G800Raw=="
}, },
"node_modules/vue2-leaflet": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/vue2-leaflet/-/vue2-leaflet-2.7.1.tgz",
"integrity": "sha512-K7HOlzRhjt3Z7+IvTqEavIBRbmCwSZSCVUlz9u4Rc+3xGCLsHKz4TAL4diAmfHElCQdPPVdZdJk8wPUt2fu6WQ==",
"peerDependencies": {
"@types/leaflet": "^1.5.7",
"leaflet": "^1.3.4",
"vue": "^2.5.17"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@ -28,6 +28,7 @@
"@inertiajs/inertia": "^0.11.0", "@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia-vue": "^0.8.0", "@inertiajs/inertia-vue": "^0.8.0",
"@tailwindcss/typography": "^0.5.9", "@tailwindcss/typography": "^0.5.9",
"leaflet": "^1.9.3",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merge": "^2.1.1", "merge": "^2.1.1",
"pinia": "^2.0.35", "pinia": "^2.0.35",
@ -36,6 +37,7 @@
"svg-sprite": "^2.0.2", "svg-sprite": "^2.0.2",
"v-tooltip": "^2.1.3", "v-tooltip": "^2.1.3",
"vue-toasted": "^1.1.28", "vue-toasted": "^1.1.28",
"vue2-leaflet": "^2.7.1",
"wnumb": "^1.2.0" "wnumb": "^1.2.0"
} }
} }

View File

@ -8,6 +8,7 @@ parameters:
- tests - tests
- database - database
- packages/tex/src - packages/tex/src
- packages/osm/src
- packages/laravel-nami/src - packages/laravel-nami/src
- packages/laravel-nami/tests - packages/laravel-nami/tests

25
resources/css/app.css vendored
View File

@ -1,15 +1,16 @@
@import "tailwindcss/base"; @import 'tailwindcss/base';
@import "tailwindcss/components"; @import 'tailwindcss/components';
@import "tailwindcss/utilities"; @import 'tailwindcss/utilities';
@layer components { @layer components {
@import "base"; @import 'base';
@import "switch"; @import 'switch';
@import "layout"; @import 'layout';
@import "buttons"; @import 'buttons';
@import "table"; @import 'table';
@import "sidebar"; @import 'sidebar';
@import "bool"; @import 'bool';
@import "form"; @import 'form';
@import "tooltip"; @import 'tooltip';
@import 'leaflet';
} }

666
resources/css/leaflet.css vendored Normal file
View File

@ -0,0 +1,666 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane {
z-index: 400;
}
.leaflet-tile-pane {
z-index: 200;
}
.leaflet-overlay-pane {
z-index: 400;
}
.leaflet-shadow-pane {
z-index: 500;
}
.leaflet-marker-pane {
z-index: 600;
}
.leaflet-tooltip-pane {
z-index: 650;
}
.leaflet-popup-pane {
z-index: 700;
}
.leaflet-map-pane canvas {
z-index: 100;
}
.leaflet-map-pane svg {
z-index: 200;
}
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1);
transition: transform 0.25s cubic-bezier(0, 0, 0.25, 1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078a8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255, 255, 255, 0.5);
}
/* general typography */
.leaflet-container {
font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in,
.leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(../img/leaflet/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(../img/leaflet/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
/* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(../img/leaflet/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0, 0, 0, 0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0, 0, 0, 0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: 'progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)';
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: '';
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@ -1,13 +1,10 @@
<template> <template>
<section <section class="p-3 rounded-lg flex flex-col" :class="{'bg-gray-800': second === false, 'bg-gray-700': second === true}">
class="p-3 rounded-lg flex flex-col"
:class="{'bg-gray-800': second === false, 'bg-gray-700': second === true}"
>
<div class="flex items-center"> <div class="flex items-center">
<heading class="col-span-full" v-if="heading">{{ heading }}</heading> <heading class="col-span-full" v-if="heading">{{ heading }}</heading>
<slot name="in-title"></slot> <slot name="in-title"></slot>
</div> </div>
<main :class="{'mt-2': heading, 'containerClass': true}"> <main :class="{'mt-2': heading, [containerClass]: true}">
<slot></slot> <slot></slot>
</main> </main>
</section> </section>

View File

@ -48,8 +48,9 @@
<payments :value="inner.payments"></payments> <payments :value="inner.payments"></payments>
</box> </box>
<box heading="Karte" container-class="grow" class="area-map hidden xl:block"> <box heading="Karte" container-class="grow" class="area-map hidden xl:flex">
<vmap :inner="inner"></vmap> <vmap v-if="inner.lat && inner.lon" :value="[inner.lat, inner.lon]"></vmap>
<div class="h-full flex items-center justify-center text-gray-400 text-center" v-else>Keine Karte vorhanden</div>
</box> </box>
</div> </div>
</page-layout> </page-layout>

View File

@ -1,21 +1,43 @@
<template> <template>
<iframe <l-map style="height: 100%" :zoom="zoom" :center="center">
class="grow" <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
width="100%" <l-marker :lat-lng="markerLatLng"></l-marker>
height="100%" </l-map>
frameborder="0"
scrolling="no"
marginheight="0"
marginwidth="0"
src="https://www.openstreetmap.org/export/embed.html?bbox=9.699318408966066%2C47.484177893725764%2C9.729595184326174%2C47.49977861091604&amp;layer=mapnik&amp;marker=47.49197883161885%2C9.714467525482178"
>
</iframe>
</template> </template>
<script> <script>
import {LMap, LTileLayer, LMarker} from 'vue2-leaflet';
import {Icon} from 'leaflet';
delete Icon.Default.prototype._getIconUrl;
Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
iconUrl: require('leaflet/dist/images/marker-icon.png'),
shadowUrl: require('leaflet/dist/images/marker-shadow.png'),
});
export default { export default {
props: { props: {
inner: {}, value: {
required: true,
validator(f) {
return f.length === 2;
},
},
},
components: {
LMap,
LTileLayer,
LMarker,
},
data() {
return {
url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
zoom: 15,
center: [...this.value],
markerLatLng: [...this.value],
};
}, },
}; };
</script> </script>

View File

@ -0,0 +1,28 @@
<?php
namespace Tests\Feature\Member;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
class NominatimTest extends TestCase
{
use DatabaseTransactions;
public function testItGetsCoordinates(): void
{
Member::enableGeolocation();
Http::fake([
'https://nominatim.openstreetmap.org/search/*' => Http::response('[{"place_id":262556558,"osm_type":"way","osm_id":785100564,"lat":"51.1775766","lon":"7.025311390606571"}]', 200),
]);
$this->login()->loginNami();
$member = Member::factory()->defaults()->create(['address' => 'Itterstr 11', 'zip' => '55667', 'location' => 'Koln']);
$this->assertEquals(51.1775766, $member->fresh()->lat);
$this->assertEquals(7.0253113906066, $member->fresh()->lon);
Http::assertSent(fn ($request) => 'https://nominatim.openstreetmap.org/search/Itterstr%2011%2C%2055667%20Koln?format=json&addressdetails=1' === $request->url());
}
}

View File

@ -72,6 +72,8 @@ class ShowTest extends TestCase
'send_newspaper' => true, 'send_newspaper' => true,
'joined_at' => '2022-06-11', 'joined_at' => '2022-06-11',
'mitgliedsnr' => 998, 'mitgliedsnr' => 998,
'lon' => 19.05,
'lat' => 14.053,
]); ]);
$response = $this->get("/member/{$member->id}"); $response = $this->get("/member/{$member->id}");
@ -105,6 +107,8 @@ class ShowTest extends TestCase
'joined_at_human' => '11.06.2022', 'joined_at_human' => '11.06.2022',
'bill_kind_name' => 'Post', 'bill_kind_name' => 'Post',
'mitgliedsnr' => 998, 'mitgliedsnr' => 998,
'lon' => 19.05,
'lat' => 14.053,
'subscription' => [ 'subscription' => [
'name' => 'Sub', 'name' => 'Sub',
], ],

View File

@ -27,6 +27,7 @@ abstract class TestCase extends BaseTestCase
{ {
parent::setUp(); parent::setUp();
Auth::fake(); Auth::fake();
Member::disableGeolocation();
} }
public function loginNami(int $mglnr = 12345, string $password = 'password', int|Group $groupId = 55): self public function loginNami(int $mglnr = 12345, string $password = 'password', int|Group $groupId = 55): self

5
webpack.mix.js vendored
View File

@ -3,6 +3,8 @@ const tailwindcss = require('tailwindcss');
const atImport = require('postcss-import'); const atImport = require('postcss-import');
const nested = require('tailwindcss/nesting'); const nested = require('tailwindcss/nesting');
mix.setResourceRoot('/');
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Mix Asset Management | Mix Asset Management
@ -17,5 +19,4 @@ const nested = require('tailwindcss/nesting');
mix.js('resources/js/app.js', 'public/js') mix.js('resources/js/app.js', 'public/js')
.vue({version: 2}) .vue({version: 2})
.postCss('resources/css/app.css', 'public/css', [atImport(), nested(), tailwindcss('./tailwind.config.js')]) .postCss('resources/css/app.css', 'public/css', [atImport(), nested(), tailwindcss('./tailwind.config.js')])
.copy('resources/img', 'public/img') .copy('resources/img', 'public/img');
.sourceMaps();