diff --git a/app/Device.php b/app/Device.php index 931f6572d..dd5a37078 100644 --- a/app/Device.php +++ b/app/Device.php @@ -309,7 +309,11 @@ public function uWasteDiverted() { $wasteDiverted = 0; - if ($this->isFixed() && $this->deviceCategory->isUnpowered()) { + $unpowered = \Cache::remember('category-unpowered-' . $this->category, 60, function() { + return $this->deviceCategory->isUnpowered(); + }); + + if ($this->isFixed() && $unpowered) { if ($this->estimate > 0) { $wasteDiverted = $this->estimate; } else { diff --git a/app/Http/Controllers/API/GroupController.php b/app/Http/Controllers/API/GroupController.php index 655ef476b..84c19a11a 100644 --- a/app/Http/Controllers/API/GroupController.php +++ b/app/Http/Controllers/API/GroupController.php @@ -240,6 +240,15 @@ public static function getGroupList() * type="boolean" * ) * ), + * @OA\Parameter( + * name="includeNextEvent", + * description="Include the next event for the group. This makes the call slower. Default false.", + * required=false, + * in="query", + * @OA\Schema( + * type="boolean" + * ) + * ), * @OA\Response( * response=200, * description="Successful operation", @@ -247,12 +256,26 @@ public static function getGroupList() * @OA\Property( * property="data", * title="data", - * description="An array of group names", + * description="An array of basic group info", * type="array", * @OA\Items( * type="object", * @OA\Property(property="id", type="integer", example=1), * @OA\Property(property="name", type="string", example="Group Name"), + * @OA\Property( + * property="lat", + * title="lat", + * description="Latitude of the group.", + * format="float", + * example="50.8113243" + * ), + * @OA\Property( + * property="lng", + * title="lng", + * description="Longitude of the group.", + * format="float", + * example="-1.0788839" + * ), * ) * ) * ) @@ -265,8 +288,8 @@ public static function listNamesv2(Request $request) { 'includeArchived' => ['string', 'in:true,false'], ]); - // We only return the group id and name, for speed. - $query = Group::select('idgroups', 'name', 'archived_at'); + // We only return a small number of attributes, for speed. + $query = Group::select('idgroups', 'name', 'latitude', 'longitude', 'archived_at'); if (!$request->has('includeArchived') || $request->get('includeArchived') == 'false') { $query = $query->whereNull('archived_at'); @@ -279,6 +302,8 @@ public static function listNamesv2(Request $request) { $ret[] = [ 'id' => $group->idgroups, 'name' => $group->name, + 'lat' => $group->latitude, + 'lng' => $group->longitude, 'archived_at' => $group->archived_at ? Carbon::parse($group->archived_at)->toIso8601String() : null ]; } @@ -288,6 +313,82 @@ public static function listNamesv2(Request $request) { ]; } + /** + * @OA\Get( + * path="/api/v2/groups/summary", + * operationId="getGroupSummariesv2", + * tags={"Groups"}, + * summary="Get list of groups with summary information", + * @OA\Parameter( + * name="archived", + * description="Include archived groups", + * required=false, + * in="path", + * @OA\Schema( + * type="boolean" + * ) + * ), + * @OA\Parameter( + * name="includeNextEvent", + * description="Include the next event for the group. This makes the call slower. Default false.", + * required=false, + * in="query", + * @OA\Schema( + * type="boolean" + * ) + * ), + * @OA\Parameter( + * name="includeCounts", + * description="Include the counts of hosts and restarters. This makes the call slower. Default false.", + * required=false, + * in="query", + * @OA\Schema( + * type="boolean" + * ) + * ), + * @OA\Parameter( + * name="includeCounts", + * description="Include impact stats. This makes the call slower. Default true.", + * required=false, + * in="query", + * @OA\Schema( + * type="boolean" + * ) + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * @OA\Property( + * property="data", + * title="data", + * description="An array of events", + * type="array", + * @OA\Items( + * @OA\Schema( + * ref="#/components/schemas/GroupSummary" + * ), + * ) + * ) + * ) + * ), + * ) + */ + + public static function listSummaryv2(Request $request) { + $request->validate([ + 'archived' => ['string', 'in:true,false'], + ]); + + $query = Group::all(); + + $groups = $query->all(); + + return [ + 'data' => \App\Http\Resources\GroupSummaryCollection::make($groups) + ]; + } + /** * @OA\Get( * path="/api/v2/groups/tags", diff --git a/app/Http/Controllers/GroupController.php b/app/Http/Controllers/GroupController.php index 4e6b197e2..1e16f9a9e 100644 --- a/app/Http/Controllers/GroupController.php +++ b/app/Http/Controllers/GroupController.php @@ -44,18 +44,13 @@ private function indexVariations($tab, $network) //Get current logged in user $user = Auth::user(); - // Get all groups - $groups = Group::with(['networks']) - ->orderBy('name', 'ASC') - ->get(); - // Get all group tags $all_group_tags = GroupTags::all(); $networks = Network::all(); // Look for groups we have joined, not just been invited to. We have to explicitly test on deleted_at because // the normal filtering out of soft deletes won't happen for joins. - $your_groups =array_column(Group::with(['networks']) + $your_groups = array_column(Group::with(['networks']) ->join('users_groups', 'users_groups.group', '=', 'groups.idgroups') ->leftJoin('events', 'events.group', '=', 'groups.idgroups') ->where('users_groups.user', $user->id) @@ -66,13 +61,40 @@ private function indexVariations($tab, $network) ->get() ->toArray(), 'idgroups'); - // We pass a high limit to the groups nearby; there is a distance limit which will normally kick in first. - $groups_near_you = array_column($user->groupsNearby(1000), 'idgroups'); + $nearby_groups = []; + $min_lat = 90; + $max_lat = -90; + $min_lng = 180; + $max_lng = -180; + + if ($user->latitude || $user->longitude || $user->country_code) { + // We pass a high limit to the groups nearby; there is a distance limit which will normally kick in first. + $nearby_groups = $user->groupsNearby(1000); + + // Now find the lat/lng bounding box which contains these groups. + foreach ($nearby_groups as $group) { + if ($group->latitude < $min_lat) { + $min_lat = $group->latitude; + } + if ($group->latitude > $max_lat) { + $max_lat = $group->latitude; + } + if ($group->longitude < $min_lng) { + $min_lng = $group->longitude; + } + if ($group->longitude > $max_lng) { + $max_lng = $group->longitude; + } + } + } return view('group.index', [ - 'groups' => GroupController::expandGroups($groups, $your_groups, $groups_near_you), + 'your_groups' => $your_groups, + 'nearby_groups' => [ [ $min_lat, $min_lng ], [ $max_lat, $max_lng ] ], 'your_area' => $user->location, - 'tab' => $tab, + 'your_lat' => $user->latitude, + 'your_lng' => $user->longitude, + 'tab' => (!$tab || $tab === 'mine') ? 'mine' : 'other', 'network' => $network, 'networks' => $networks, 'all_group_tags' => $all_group_tags, @@ -484,7 +506,7 @@ public function delete($id) } } - public static function expandGroups($groups, $your_groupids, $nearby_groupids) + public static function expandGroups($groups, $your_groupids) { $ret = []; $user = Auth::user(); @@ -530,7 +552,6 @@ public static function expandGroups($groups, $your_groupids, $nearby_groupids) 'networks' => \Illuminate\Support\Arr::pluck($group->networks, 'id'), 'group_tags' => $group->group_tags()->get()->pluck('id'), 'following' => in_array($group->idgroups, $your_groupids), - 'nearby' => in_array($group->idgroups, $nearby_groupids), 'archived_at' => $group->archived_at ? Carbon::parse($group->archived_at)->toIso8601String() : null ]; } diff --git a/app/Http/Controllers/NetworkController.php b/app/Http/Controllers/NetworkController.php index a605faa87..4304331f2 100644 --- a/app/Http/Controllers/NetworkController.php +++ b/app/Http/Controllers/NetworkController.php @@ -58,9 +58,38 @@ public function show(Network $network) $groupsForAssociating = $network->groupsNotIn()->sortBy('name'); } + // Find the lat/lng bounding box for all groups in this network. + $minLat = $minLng = $maxLat = $maxLng = null; + + foreach ($network->groups as $group) { + $lat = $group->latitude; + $lng = $group->longitude; + + if (is_null($minLat) || $lat < $minLat) { + $minLat = $lat; + } + + if (is_null($minLng) || $lng < $minLng) { + $minLng = $lng; + } + + if (is_null($maxLat) || $lat > $maxLat) { + $maxLat = $lat; + } + + if (is_null($maxLng) || $lng > $maxLng) { + $maxLng = $lng; + } + } + + return view('networks.show', [ 'network' => $network, 'groupsForAssociating' => $groupsForAssociating, + 'mapBounds' => [ + [ $minLat, $minLng ], + [ $maxLat, $maxLng ], + ], ]); } diff --git a/app/Http/Resources/Group.php b/app/Http/Resources/Group.php index 211a9088f..bbf79542a 100644 --- a/app/Http/Resources/Group.php +++ b/app/Http/Resources/Group.php @@ -274,9 +274,11 @@ class Group extends JsonResource */ public function toArray($request) { - $stats = $this->resource->getGroupStats(); - $stats['events'] = $stats['parties']; - unset($stats['parties']); + if ($request->get('includeStats', true)) { + $stats = $this->resource->getGroupStats(); + $stats['events'] = $stats['parties']; + unset($stats['parties']); + } $networkData = gettype($this->network_data) == 'string' ? json_decode($this->network_data, true) : $this->network_data; @@ -288,7 +290,7 @@ public function toArray($request) $ret = [ 'id' => $this->idgroups, 'name' => $this->name, - 'image' => $this->groupImage && is_object($this->groupImage) && is_object($this->groupImage->image) ? $this->groupImage->image->path : null, + 'image' => $this->image, 'website' => $this->website, 'phone' => $this->phone, 'description' => $this->free_text, diff --git a/app/Http/Resources/GroupSummary.php b/app/Http/Resources/GroupSummary.php index 2d8a493d9..52b8ce815 100644 --- a/app/Http/Resources/GroupSummary.php +++ b/app/Http/Resources/GroupSummary.php @@ -4,6 +4,7 @@ use Carbon\Carbon; use Illuminate\Http\Resources\Json\JsonResource; +use Cache; /** * @OA\Schema( @@ -62,6 +63,18 @@ * ref="#/components/schemas/EventSummary" * ), * @OA\Property( + * property="hosts", + * title="hosts", + * description="The number of hosts of this group (if requested via API call flag).", + * type="number", + * ), + * @OA\Property( + * property="restarters", + * title="hosts", + * description="The number of restarters in this group (if requested via API call flag).", + * type="number", + * ), + * @OA\Property( * property="summary", * title="summary", * description="Indicates that this is a summary result, not full group information.", @@ -90,7 +103,7 @@ public function toArray($request) $ret = [ 'id' => $this->idgroups, 'name' => $this->name, - 'image' => $this->groupImage && is_object($this->groupImage) && is_object($this->groupImage->image) ? $this->groupImage->image->path : null, + 'image' => $this->image, 'location' => new GroupLocation($this), 'networks' => new NetworkSummaryCollection($this->resource->networks), 'updated_at' => Carbon::parse($this->updated_at)->toIso8601String(), @@ -98,27 +111,53 @@ public function toArray($request) 'summary' => true ]; + if ($request->get('includeCounts', false)) { + $ret['hosts'] = $this->resource->all_confirmed_hosts_count; + $ret['restarters'] = $this->resource->all_confirmed_restarters_count; + } + if ($request->get('includeNextEvent', false)) { - // Get next approved event for group. - $nextevent = \App\Group::find($this->idgroups)->getNextUpcomingEvent(); + // Get next approved event for group. We cache all upcoming events to speed up the case where we + // are fetching many groups. + if (Cache::has('future_events')) { + $upcoming = Cache::get('future_events'); + } else { + $future = \App\Party::future()->get(); - if ($nextevent) { - // Using the resource for the nested event causes infinite loops. Just add the model attributes we - // need directly. - $ret['next_event'] = [ - 'id' => $nextevent->idevents, - 'start' => $nextevent->event_start_utc, - 'end' => $nextevent->event_end_utc, - 'timezone' => $nextevent->timezone, - 'title' => $nextevent->venue ?? $nextevent->location, - 'location' => $nextevent->location, - 'online' => $nextevent->online, - 'lat' => $nextevent->latitude, - 'lng' => $nextevent->longitude, - 'updated_at' => $nextevent->updated_at->toIso8601String(), - 'summary' => true - ]; + // Can't serialise the whole event, and we only need a few fields. + $upcoming = []; + + foreach ($future as $event) { + $upcoming[] = [ + 'id' => $event->idevents, + 'group_id' => $event->group, + 'start' => $event->event_start_utc, + 'end' => $event->event_end_utc, + 'timezone' => $event->timezone, + 'title' => $event->venue ?? $event->location, + 'location' => $event->location, + 'online' => $event->online, + 'lat' => $event->latitude, + 'lng' => $event->longitude, + 'updated_at' => $event->updated_at->toIso8601String(), + 'summary' => true + ]; + } + + Cache::put('future_events', $upcoming, 60); + } + + // Find the next event for this group. + $nextevent = null; + + foreach ($upcoming as $event) { + if ($event['group_id'] == $this->idgroups) { + $nextevent = $event; + break; + } } + + $ret['next_event'] = $nextevent; } return($ret); diff --git a/app/User.php b/app/User.php index aa5b84d19..1c35b98e8 100644 --- a/app/User.php +++ b/app/User.php @@ -123,27 +123,40 @@ public function groups() */ public function groupsNearby($numberOfGroups = 10, $createdSince = null, $nearby = self::NEARBY_KM) { - if (is_null($this->latitude) || is_null($this->longitude)) { - return []; - } + $groups = null; - $groupsNearbyQuery = Group::select( - DB::raw('*, ( 6371 * acos( cos( radians('.$this->latitude.') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('.$this->longitude.') ) + sin( radians('.$this->latitude.') ) * sin( radians( latitude ) ) ) ) AS dist') + if (!is_null($this->latitude) && !is_null($this->longitude)) { + $groupsNearbyQuery = Group::select( + DB::raw('*, ( 6371 * acos( cos( radians('.$this->latitude.') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('.$this->longitude.') ) + sin( radians('.$this->latitude.') ) * sin( radians( latitude ) ) ) ) AS dist') )->where(function ($q) { $q->whereNull('archived_at'); - // Only show approved groups. - $q->where('approved', true); - })->having('dist', '<=', $nearby) - ->groupBy('idgroups'); + // Only show approved groups. + $q->where('approved', true); + })->having('dist', '<=', $nearby) + ->groupBy('idgroups'); - if ($createdSince) { - $groupsNearbyQuery->whereDate('created_at', '>=', date('Y-m-d', strtotime($createdSince))); - } + if ($createdSince) { + $groupsNearbyQuery->whereDate('created_at', '>=', date('Y-m-d', strtotime($createdSince))); + } - $groups = $groupsNearbyQuery->orderBy('dist', 'ASC') - ->take($numberOfGroups) - ->get(); + $groups = $groupsNearbyQuery->orderBy('dist', 'ASC') + ->take($numberOfGroups) + ->get(); + } else if ($this->country_code) { + // We have no city, but we do have a country. So all groups with this country code are nearby. + $groupsInCountry = Group::where('country_code', $this->country_code) + ->where('approved', true); + + if ($createdSince) { + $groupsInCountry->whereDate('created_at', '>=', date('Y-m-d', strtotime($createdSince))); + } + + $groups = $groupsInCountry + ->orderBy('name', 'ASC') + ->take($numberOfGroups) + ->get(); + } // Expand the image $groupsNearby = []; diff --git a/database/migrations/2024_11_25_124911_group_image.php b/database/migrations/2024_11_25_124911_group_image.php new file mode 100644 index 000000000..39f80c664 --- /dev/null +++ b/database/migrations/2024_11_25_124911_group_image.php @@ -0,0 +1,43 @@ +string('image', 255)->nullable(); + }); + + $groups = Group::all(); + + foreach ($groups as $group) { + $group_image = $group->groupImage; + + $group->image = (is_object($group_image) && is_object($group_image->image)) ? + $group_image->image->path : null; + $group->save(); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('groups', function (Blueprint $table) { + $table->dropColumn('image'); + }); + } +}; diff --git a/lang/de/groups.php b/lang/de/groups.php index 1629d89db..cea5df597 100644 --- a/lang/de/groups.php +++ b/lang/de/groups.php @@ -3,8 +3,6 @@ return [ 'group' => 'Group', 'groups' => 'Groups', - 'all_groups' => 'All groups', - 'search_name' => 'Search name', 'add_groups' => 'Add a group', 'add_groups_content' => 'Tell us more about your group, so we can create a page for you and help you publicise to potential volunteers and participants.', 'create_groups' => 'Create new group', diff --git a/lang/en/groups.php b/lang/en/groups.php index 65fc4d793..5cf87297e 100644 --- a/lang/en/groups.php +++ b/lang/en/groups.php @@ -5,16 +5,14 @@ 'headline_stats_dropdown' => 'Headline stats', 'co2_equivalence_visualisation_dropdown' => 'CO2 equivalence visualisation', 'group_admin_only' => 'Admin only', - 'search_name' => 'Search name', 'group' => 'Group', 'groups' => 'Groups', - 'all_groups' => 'All Groups', 'add_groups' => 'Add a group', 'add_groups_content' => 'Tell us more about your group, so we can create a page for you and help you publicise to potential volunteers and participants.', 'create_groups' => 'Add a new group', 'create_group' => 'Create group', 'groups_title1' => 'Your Groups', - 'groups_title2' => 'Other groups nearby', + 'groups_title2' => 'Other groups', 'groups_name' => 'Name', 'groups_name_of' => 'Name of group', 'groups_about_group' => 'Tell us about your group', @@ -103,7 +101,6 @@ 'repairable_items' => 'Repairable items', 'end_of_life_items' => 'End-of-life items', 'no_unpowered_stats' => 'At the moment, these stats are only displayed for powered items. We hope to include unpowered items soon.', - 'all_groups_mobile' => 'All', 'create_groups_mobile2' => 'Add new', 'groups_title1_mobile' => 'Yours', 'groups_title2_mobile' => 'Nearest', @@ -111,13 +108,7 @@ 'no_groups_mine' => 'If you can\'t see any here yet, why not follow your nearest group to hear about their upcoming repair events?', 'no_groups_nearest_no_location' => '

You do not currently have a town/city set. You can set one in your profile.

You can also view all groups.

', 'no_groups_nearest_with_location' => '

There are no groups within 50 km of your location. You can see all groups here. Or why not start your own? Learn what running your own repair event involves.

', - 'group_count' => 'There is :count group.|There are :count groups.', - 'search_name_placeholder' => 'Search name...', - 'search_location_placeholder' => 'Search location...', - 'search_country_placeholder' => 'Country...', - 'search_tags_placeholder' => 'Tag', - 'show_filters' => 'Show Filters', - 'hide_filters' => 'Hide Filters', + 'group_count' => 'There is :count group. Zoom out to see more.|There are :count groups. Zoom out to see more.', 'leave_group_button' => 'Unfollow group', 'leave_group_button_mobile' => 'Unfollow', 'leave_group_confirm' => 'Please confirm that you want to unfollow this group.', @@ -140,7 +131,6 @@ 'archive_group_confirm' => 'Please confirm that you want to archive :name.', 'delete_succeeded' => 'Group :name has been deleted.', 'nearest_groups' => 'These are the groups that are within 50 km of :location', - 'nearest_groups_change' => '(change)', 'invitation_pending' => 'You have an invitation to this group. Please click here if you would like to join.', 'geocode_failed' => 'Location not found. If you are unable to find the location of your group, please try a more general location (such as village/town), or a specific street address, rather than a building name.', 'discourse_title' => 'This is a discussion group for anyone who follows :group. @@ -180,4 +170,7 @@ 'export.events.items_end_of_life' => 'Items end-of-life', 'export.events.items_kg_waste_prevented' => 'kg waste prevented', 'export.events.items_kg_co2_prevent' => 'kg CO2 prevented', + 'marker_title' => 'Click for more information', + 'goto_group' => 'Go to group', + 'next_event' => 'Next event', ]; diff --git a/lang/fr-BE/groups.php b/lang/fr-BE/groups.php index 712a888b0..11ee7636f 100644 --- a/lang/fr-BE/groups.php +++ b/lang/fr-BE/groups.php @@ -4,13 +4,12 @@ 'group' => 'Repair Café', 'groups' => 'Repair Cafés', 'all_groups' => 'Tous les Repair Cafés', - 'search_name' => 'Chercher nom', 'add_groups' => 'Ajouter un nouveau Repair Café', 'add_groups_content' => 'Dites-en nous plus sur votre repair café, afin que nous puissions vous créer une page et vous aider à trouver de potentiels nouveaux bénévoles et participants', 'create_groups' => 'Créer nouveau Repair Café', 'create_group' => 'Créer un nouveau Repair Café', 'groups_title1' => 'Vos Repair Cafés', - 'groups_title2' => 'Repair Cafés proches de chez vous', + 'groups_title2' => 'Autres Repair Cafés', 'groups_name' => 'Nom', 'groups_name_of' => 'Nom du Repair Café', 'groups_about_group' => 'Parlez-nous de votre Repair Café', @@ -104,12 +103,10 @@ 'volunteers_attended' => 'Bénévoles ayant participé', 'volunteers_confirmed' => 'Bénévoles confirmés', 'volunteers_invited' => 'Bénévoles invités', - 'all_groups_mobile' => 'Tous', 'create_groups_mobile2' => 'Ajouter nouveau', 'groups_title1_mobile' => 'Le vôtre', 'groups_title2_mobile' => 'Le plus proche', - 'group_count' => 'Il y a :count Repair Café. Il y a :count Repair Cafés.', - 'hide_filters' => 'Cacher les filtres', + 'group_count' => 'Il y a :count Repair Café. Zoom arrière pour en voir plus.|Il y a :count Repair Cafés. Zoom arrière pour en voir plus.', 'join_group_button_mobile' => 'Suivre', 'leave_group_button' => 'Ne plus suivre ce Repair Café', 'leave_group_button_mobile' => 'Ne plus suivre', @@ -120,11 +117,6 @@ 'no_groups_nearest_no_location' => '

Vous n\'avez pas défini de village/ville. Vous pouvez en ajouter un.e dans votre profil.

Vous pouvez aussi voir tous les Repair Cafés.

', 'no_groups_nearest_with_location' => '

Il n\'y a apparemment pas encore de Repair Cafés listé proche de chez vous.

Voulez-vous créer ou ajouter un Repair Café? Regardez comment faire dans nos ressources.

', 'no_unpowered_stats' => 'Pour l\'instant, ces statistiques sont seulement affichées pour les appareils électriques. Nous espérons pouvoir inclure les appareils non-électriques sous peu.', - 'search_country_placeholder' => 'Pays', - 'search_location_placeholder' => 'Rechercher localisation...', - 'search_name_placeholder' => 'Rechercher nom...', - 'search_tags_placeholder' => 'Tag', - 'show_filters' => 'Montrer les filtres', 'all' => 'Tous', 'nearby' => 'Proche', 'no_other_events' => 'Il n\'y a actuellement aucun autre événement à venir', @@ -149,7 +141,6 @@ Apprenez à utiliser ce groupe ici : :help.', 'invitation_pending' => 'Vous avez une invitation à rejoindre ce Repair Café. Cliquez ici si vous voulez le rejoindre.', 'nearest_groups' => 'Ce sont les Repair Cafés qui se trouvent dans un rayon de 50km autour de :location', - 'nearest_groups_change' => '(change)', 'talk_group' => 'Voir la conversation de Repair Café', 'talk_group_add_title' => 'Bienvenue sur :group_name', 'editing' => 'Modification de', @@ -183,4 +174,7 @@ 'export.events.items_end_of_life' => 'Total des appareils en fin de vie', 'export.events.items_kg_waste_prevented' => 'kg déchets évités', 'export.events.items_kg_co2_prevent' => 'kg emissions de CO2 évitées', + 'marker_title' => 'Cliquez pour plus d\'informations', + 'goto_group' => 'Aller au Repair Café', + 'next_event' => 'Prochain événement', ]; diff --git a/lang/fr/groups.php b/lang/fr/groups.php index b336b6474..58bf4cf74 100644 --- a/lang/fr/groups.php +++ b/lang/fr/groups.php @@ -4,13 +4,12 @@ 'group' => 'Repair Café', 'groups' => 'Repair Cafés', 'all_groups' => 'Tous les Repair Cafés', - 'search_name' => 'Chercher nom', 'add_groups' => 'Ajouter un nouveau Repair Café', 'add_groups_content' => 'Dites-en nous plus sur votre repair café, afin que nous puissions vous créer une page et vous aider à trouver de potentiels nouveaux bénévoles et participants', 'create_groups' => 'Créer nouveau Repair Café', 'create_group' => 'Créer un nouveau Repair Café', 'groups_title1' => 'Vos Repair Cafés', - 'groups_title2' => 'Repair Cafés proches de chez vous', + 'groups_title2' => 'Autres Repair Cafés', 'groups_name' => 'Nom', 'groups_name_of' => 'Nom du Repair Café', 'groups_about_group' => 'Parlez-nous de votre Repair Café', @@ -104,12 +103,10 @@ 'volunteers_attended' => 'Bénévoles ayant participé', 'volunteers_confirmed' => 'Bénévoles confirmés', 'volunteers_invited' => 'Bénévoles invités', - 'all_groups_mobile' => 'Tous', 'create_groups_mobile2' => 'Ajouter nouveau', 'groups_title1_mobile' => 'Le vôtre', 'groups_title2_mobile' => 'Le plus proche', - 'group_count' => 'Il y a :count Repair Café. Il y a :count Repair Cafés.', - 'hide_filters' => 'Cacher les filtres', + 'group_count' => 'Il y a :count Repair Café. Zoom arrière pour en voir plus.|Il y a :count Repair Cafés. Zoom arrière pour en voir plus.', 'join_group_button_mobile' => 'Suivre', 'leave_group_button' => 'Ne plus suivre ce Repair Café', 'leave_group_button_mobile' => 'Ne plus suivre', @@ -120,11 +117,6 @@ 'no_groups_nearest_no_location' => '

Vous n\'avez pas défini de village/ville. Vous pouvez en ajouter un.e dans votre profil.

Vous pouvez aussi voir tous les Repair Cafés.

', 'no_groups_nearest_with_location' => '

Il n\'y a apparemment pas encore de Repair Cafés listé proche de chez vous.

Voulez-vous créer ou ajouter un Repair Café? Regardez comment faire dans nos ressources.

', 'no_unpowered_stats' => 'Pour l\'instant, ces statistiques sont seulement affichées pour les appareils électriques. Nous espérons pouvoir inclure les appareils non-électriques sous peu.', - 'search_country_placeholder' => 'Pays', - 'search_location_placeholder' => 'Rechercher localisation...', - 'search_name_placeholder' => 'Rechercher nom...', - 'search_tags_placeholder' => 'Tag', - 'show_filters' => 'Montrer les filtres', 'all' => 'Tous', 'nearby' => 'Proche', 'no_other_events' => 'Il n\'y a actuellement aucun autre événement à venir', @@ -149,7 +141,6 @@ Apprenez à utiliser ce groupe ici : :help.', 'invitation_pending' => 'Vous avez une invitation à rejoindre ce Repair Café. Cliquez ici si vous voulez le rejoindre.', 'nearest_groups' => 'Ce sont les Repair Cafés qui se trouvent dans un rayon de 50km autour de :location', - 'nearest_groups_change' => '(change)', 'talk_group' => 'Voir la conversation de Repair Café', 'talk_group_add_title' => 'Bienvenue sur :group_name', 'editing' => 'Modification de', @@ -183,4 +174,7 @@ 'export.events.items_end_of_life' => 'Total des appareils en fin de vie', 'export.events.items_kg_waste_prevented' => 'kg déchets évités', 'export.events.items_kg_co2_prevent' => 'kg emissions de CO2 évitées', + 'marker_title' => 'Cliquez pour plus d\'informations', + 'goto_group' => 'Aller au Repair Café', + 'next_event' => 'Prochain événement', ]; diff --git a/lang/it/groups.php b/lang/it/groups.php index 2a453fcb6..df51cc197 100644 --- a/lang/it/groups.php +++ b/lang/it/groups.php @@ -4,7 +4,6 @@ 'group' => 'Gruppo', 'groups' => 'Gruppi', 'all_groups' => 'Tutti i gruppi', - 'search_name' => 'Cerca nome', 'add_groups' => 'Aggiungi un gruppo', 'add_groups_content' => 'Raccontaci di più sul tuo gruppo, in modo che possiamo creare una pagina per te e aiutarti a trovare volontari e partecipanti.', 'create_groups' => 'Crea nuovo gruppo', diff --git a/lang/ne/groups.php b/lang/ne/groups.php index f8e5f293b..f6d39cb80 100644 --- a/lang/ne/groups.php +++ b/lang/ne/groups.php @@ -41,7 +41,6 @@ 'message_example_text' => '', 'message_header' => 'Uitnodiging', 'restarter_column_table' => 'Restarter', - 'search_name' => 'Naam zoeken', 'send_invite_button' => 'Uitnodigingen versturen', 'share_stats_header' => 'Je statistieken delen', 'share_stats_message' => 'Door kapotte toestellen te repareren :group heel wat CO2 uitstoot en afval vermeden. Help ons om dit goede nieuws te verspreiden en deel het op je website.', diff --git a/lang/nl-BE/groups.php b/lang/nl-BE/groups.php index b2cd84bf2..ebebf12fd 100644 --- a/lang/nl-BE/groups.php +++ b/lang/nl-BE/groups.php @@ -41,7 +41,6 @@ 'message_example_text' => '', 'message_header' => 'Uitnodiging', 'restarter_column_table' => 'Restarter', - 'search_name' => 'Naam zoeken', 'send_invite_button' => 'Uitnodigingen versturen', 'share_stats_header' => 'Je statistieken delen', 'share_stats_message' => 'Door kapotte toestellen te repareren heeft :group heel wat CO2 uitstoot en afval vermeden. Help ons om dit goede nieuws te verspreiden en deel het op je website.', diff --git a/lang/no/groups.php b/lang/no/groups.php index 2607fc6e1..bc0d4ca5e 100644 --- a/lang/no/groups.php +++ b/lang/no/groups.php @@ -3,8 +3,6 @@ return [ 'group' => 'Group', 'groups' => 'Groups', - 'all_groups' => 'All groups', - 'search_name' => 'Search name', 'add_groups' => 'Add a group', 'add_groups_content' => 'Tell us more about your group, so we can create a page for you and help you publicise to potential volunteers and participants.', 'create_groups' => 'Create new group', diff --git a/package-lock.json b/package-lock.json index 09a81bc72..3ad0abd04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@sentry/tracing": "^6.7.2", "@sentry/vue": "^6.7.2", + "@vectorial1024/leaflet-color-markers": "^2.0.4", "ajv": "^6.12.3", "babel-plugin-istanbul": "^6.1.1", "bootstrap-fileinput": "^4.4.9", @@ -27,6 +28,7 @@ "js-cookie": "^2.2.0", "lang.js": "^1.1.14", "leaflet": "^1.4.0", + "leaflet-control-geocoder": "^1.13.0", "moment": "^2.29.4", "moment-timezone": "^0.5.35", "npm": "^9.4.0", @@ -40,7 +42,7 @@ "sortablejs": "^1.7.0", "text-clipper": "^2.1.0", "tokenfield": "^0.9.10", - "vue-awesome": "^4.5.0", + "vue-awesome": "^4.1.0", "vue-clipboard2": "^0.3.1", "vue-cookies": "^1.7.4", "vue-google-autocomplete": "^1.1.1", @@ -83,6 +85,8 @@ "resolve-url-loader": "^5.0.0", "sass": "^1.56.1", "sass-loader": "^12.6.0", + "ts-loader": "^9.5.1", + "typescript": "^5.6.3", "v8-to-istanbul": "^8.0.0", "vue": "^2.7.14", "vue-loader": "^15.10.1", @@ -3845,6 +3849,14 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", "dev": true }, + "node_modules/@vectorial1024/leaflet-color-markers": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vectorial1024/leaflet-color-markers/-/leaflet-color-markers-2.0.4.tgz", + "integrity": "sha512-lAYvIG/FXMQjNR7SjN0uGowpGkEwJobrmnUUH4q/ViRyhw/3n1K03a2GW0TTgLpi2OVLOFJfVJz4j+NZeXYj5A==", + "peerDependencies": { + "leaflet": "^1.0.0" + } + }, "node_modules/@vue/compiler-sfc": { "version": "2.7.14", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", @@ -5408,6 +5420,19 @@ "node": ">=8.0.0" } }, + "node_modules/browser-sync-client/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/browser-sync-ui": { "version": "2.27.11", "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.11.tgz", @@ -12969,6 +12994,14 @@ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz", "integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA==" }, + "node_modules/leaflet-control-geocoder": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/leaflet-control-geocoder/-/leaflet-control-geocoder-1.13.0.tgz", + "integrity": "sha512-mgYGx/2WA5CcvhP+IJtw7VvJwSGAe5zxX+TKe6ruYkLj2W5I5V/K/nQiLvsUtqifBojBGoKIPNZ8m0mXJNIudg==", + "optionalDependencies": { + "open-location-code": "^1.0.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -16693,6 +16726,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open-location-code": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/open-location-code/-/open-location-code-1.0.3.tgz", + "integrity": "sha512-DBm14BSn40Ee241n80zIFXIT6+y8Tb0I+jTdosLJ8Sidvr2qONvymwqymVbHV2nS+1gkDZ5eTNpnOIVV0Kn2fw==", + "optional": true + }, "node_modules/open/node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -19728,6 +19767,35 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/tsconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", @@ -19792,16 +19860,16 @@ } }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ua-parser-js": { diff --git a/package.json b/package.json index 45285b216..4ba830631 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,8 @@ "resolve-url-loader": "^5.0.0", "sass": "^1.56.1", "sass-loader": "^12.6.0", + "ts-loader": "^9.5.1", + "typescript": "^5.6.3", "v8-to-istanbul": "^8.0.0", "vue": "^2.7.14", "vue-loader": "^15.10.1", @@ -48,6 +50,7 @@ "dependencies": { "@sentry/tracing": "^6.7.2", "@sentry/vue": "^6.7.2", + "@vectorial1024/leaflet-color-markers": "^2.0.4", "ajv": "^6.12.3", "babel-plugin-istanbul": "^6.1.1", "bootstrap-fileinput": "^4.4.9", @@ -68,6 +71,7 @@ "js-cookie": "^2.2.0", "lang.js": "^1.1.14", "leaflet": "^1.4.0", + "leaflet-control-geocoder": "^1.13.0", "moment": "^2.29.4", "moment-timezone": "^0.5.35", "npm": "^9.4.0", diff --git a/resources/global/css/_global.scss b/resources/global/css/_global.scss index 951313952..a9cc4080c 100644 --- a/resources/global/css/_global.scss +++ b/resources/global/css/_global.scss @@ -141,4 +141,20 @@ h2 { .fa-fw { width: 1rem; height: 1rem; -} \ No newline at end of file +} + +.fa-spin { + animation-name: spin; + animation-duration: 4s; + animation-timing-function: linear; + animation-iteration-count: infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/resources/js/app.js b/resources/js/app.js index 45ebcedd5..17c4b7593 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -54,6 +54,9 @@ import RichTextEditor from './components/RichTextEditor' import Notifications from './components/Notifications' import GroupTimeZone from './components/GroupTimeZone' import StatsShare from './components/StatsShare.vue' +import GroupMapAndList from './components/GroupMapAndList.vue' +import GroupMarker from './components/GroupMarker.vue' +import GroupInfoModal from './components/GroupInfoModal.vue' // Without this, the default map marker doesn't appear in production. Fairly well-known problem. // eslint-disable-next-line @@ -1325,6 +1328,9 @@ jQuery(document).ready(function () { 'notifications': Notifications, 'grouptimezone': GroupTimeZone, 'statsshare': StatsShare, + 'groupmapandlist': GroupMapAndList, + 'groupmarker': GroupMarker, + 'groupinfomodal': GroupInfoModal, } }) }) diff --git a/resources/js/components/GroupInfoModal.vue b/resources/js/components/GroupInfoModal.vue new file mode 100644 index 000000000..24f1e158d --- /dev/null +++ b/resources/js/components/GroupInfoModal.vue @@ -0,0 +1,93 @@ + + + diff --git a/resources/js/components/GroupMap.vue b/resources/js/components/GroupMap.vue new file mode 100644 index 000000000..fc9f1c95a --- /dev/null +++ b/resources/js/components/GroupMap.vue @@ -0,0 +1,295 @@ + + + diff --git a/resources/js/components/GroupMapAndList.vue b/resources/js/components/GroupMapAndList.vue new file mode 100644 index 000000000..a54fdfb81 --- /dev/null +++ b/resources/js/components/GroupMapAndList.vue @@ -0,0 +1,102 @@ + + + diff --git a/resources/js/components/GroupMarker.vue b/resources/js/components/GroupMarker.vue new file mode 100644 index 000000000..4befada9a --- /dev/null +++ b/resources/js/components/GroupMarker.vue @@ -0,0 +1,80 @@ + + + diff --git a/resources/js/components/GroupPage.vue b/resources/js/components/GroupPage.vue index deb47cd64..8f852cd1e 100644 --- a/resources/js/components/GroupPage.vue +++ b/resources/js/components/GroupPage.vue @@ -177,6 +177,7 @@ export default { this.initialGroup.candemote = this.candemote this.initialGroup.ingroup = this.ingroup + console.log('Group page initial group', this.initialGroup) this.$store.dispatch('groups/set', this.initialGroup) this.events.forEach(e => { diff --git a/resources/js/components/GroupsPage.vue b/resources/js/components/GroupsPage.vue index e4f1693a1..494f19690 100644 --- a/resources/js/components/GroupsPage.vue +++ b/resources/js/components/GroupsPage.vue @@ -27,7 +27,7 @@
{{ __('groups.groups_title2') }}
-

- {{ nearestGroups }} - {{ __('groups.nearest_groups_change') }}. -

- +
- - - -
diff --git a/resources/js/components/GroupsRequiringModeration.vue b/resources/js/components/GroupsRequiringModeration.vue index 6dfa2aeb8..7719c3be6 100644 --- a/resources/js/components/GroupsRequiringModeration.vue +++ b/resources/js/components/GroupsRequiringModeration.vue @@ -2,7 +2,8 @@

{{ __('groups.groups_title_admin') }}

- + TODO +
diff --git a/resources/js/components/GroupsTable.vue b/resources/js/components/GroupsTable.vue index da6d0519f..2ec01e98e 100644 --- a/resources/js/components/GroupsTable.vue +++ b/resources/js/components/GroupsTable.vue @@ -1,70 +1,37 @@