9/26 マージ
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 13s
All checks were successful
Deploy preview (main_watanabe) / deploy (push) Successful in 13s
This commit is contained in:
parent
52f568c5f4
commit
eed5d85741
@ -31,14 +31,10 @@ class ParkingSearchController extends Controller
|
|||||||
// 検索処理
|
// 検索処理
|
||||||
public function getParkData($city_name, $station_neighbor_station, $park_name)
|
public function getParkData($city_name, $station_neighbor_station, $park_name)
|
||||||
{
|
{
|
||||||
// 検索仕様
|
|
||||||
// 駐輪場マスタの全件(条件を絞った場合はその条件に一致するもの)を取得。
|
|
||||||
// 併せて各マスタから追加情報を取得するが、その際のレコードは全て1対1で結びつく想定で暫定実装する
|
|
||||||
// ※設計書に詳細な記載なし。DBの定義上は1対多の可能性もあるが、その場合現在の画面イメージと矛盾するため、実態として無い想定で進める
|
|
||||||
|
|
||||||
// 駐輪場情報検索
|
// 駐輪場情報検索
|
||||||
$park = \DB::table('park as p')
|
$park = \DB::table('park as p')
|
||||||
->select(
|
->select(
|
||||||
|
'p.park_id',
|
||||||
'p.park_name',
|
'p.park_name',
|
||||||
'p.park_adrs',
|
'p.park_adrs',
|
||||||
'p.price_memo',
|
'p.price_memo',
|
||||||
@ -49,15 +45,12 @@ class ParkingSearchController extends Controller
|
|||||||
'p.update_grace_period_end_date',
|
'p.update_grace_period_end_date',
|
||||||
'p.update_grace_period_end_time',
|
'p.update_grace_period_end_time',
|
||||||
'c.city_name',
|
'c.city_name',
|
||||||
's.station_neighbor_station',
|
's.station_neighbor_station'
|
||||||
'z.psection_id',
|
|
||||||
'z.zone_standard'
|
|
||||||
)
|
)
|
||||||
->leftJoin('city as c', 'p.city_id', '=', 'c.city_id')
|
->leftJoin('city as c', 'p.city_id', '=', 'c.city_id')
|
||||||
->leftJoin(\DB::raw(
|
->leftJoin(\DB::raw(
|
||||||
'(SELECT park_id, station_neighbor_station FROM station WHERE station_id IN (SELECT MAX(station_id) FROM station GROUP BY park_id)) as s'
|
'(SELECT park_id, station_neighbor_station FROM station WHERE station_id IN (SELECT MAX(station_id) FROM station GROUP BY park_id)) as s'
|
||||||
),'p.park_id','=','s.park_id')
|
),'p.park_id','=','s.park_id');
|
||||||
->leftJoin('zone as z', 'p.park_id', '=', 'z.park_id');
|
|
||||||
|
|
||||||
// プルダウン指定の条件でwhere句を付与
|
// プルダウン指定の条件でwhere句を付与
|
||||||
if (!empty($city_name)) {
|
if (!empty($city_name)) {
|
||||||
@ -76,42 +69,102 @@ class ParkingSearchController extends Controller
|
|||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
foreach ($park as $row) {
|
foreach ($park as $row) {
|
||||||
|
|
||||||
// ゾーンマスタの情報から空き台数を取得する
|
// ゾーンマスタの情報を取得する
|
||||||
$vacantInfo = \DB::table('zone')
|
$zoneInfo = \DB::table('zone as z')
|
||||||
->selectRaw('SUM(zone_tolerance) - SUM(zone_number) as vacant')
|
->select(
|
||||||
->where('psection_id', $row->psection_id)
|
'z.psection_id',
|
||||||
->groupBy('psection_id')
|
'z.ptype_id',
|
||||||
->first();
|
\DB::raw('SUM(z.zone_standard) as zone_standard'),
|
||||||
|
\DB::raw('SUM(z.zone_number) as zone_number'),
|
||||||
|
\DB::raw('SUM(z.zone_tolerance) as zone_tolerance'),
|
||||||
|
'ps.psection_subject',
|
||||||
|
'pt.ptype_subject'
|
||||||
|
)
|
||||||
|
->join('ptype as pt', 'z.ptype_id', '=', 'pt.ptype_id')
|
||||||
|
->leftJoin('psection as ps', 'z.psection_id', '=', 'ps.psection_id')
|
||||||
|
->where('z.park_id', $row->park_id)
|
||||||
|
->groupBy('z.park_id', 'z.ptype_id', 'z.psection_id', 'ps.psection_subject', 'pt.ptype_subject')
|
||||||
|
->get();
|
||||||
|
|
||||||
// 定期予約マスタから予約中の台数を取得する
|
// ゾーンマスタが0件の場合、次のデータへ
|
||||||
$reservedCount = \DB::table('reserve')
|
if ($zoneInfo->isEmpty()) {
|
||||||
->where('psection_id', $row->psection_id)
|
$form_data[] = [
|
||||||
->where('valid_flag', 1)
|
'park_name' => $row->park_name,
|
||||||
->count();
|
'park_adrs' => $row->park_adrs,
|
||||||
|
'price_memo' => $row->price_memo,
|
||||||
|
'park_latitude' => $row->park_latitude,
|
||||||
|
'park_longitude' => $row->park_longitude,
|
||||||
|
'city_name' => $row->city_name,
|
||||||
|
'station_neighbor_station' => $row->station_neighbor_station,
|
||||||
|
'zone_data' => []
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 更新期間内判定
|
// 更新期間内判定
|
||||||
$update_start = $row->update_grace_period_start_date . ' ' . $row->update_grace_period_start_time;
|
$update_start = $row->update_grace_period_start_date . ' ' . $row->update_grace_period_start_time;
|
||||||
$update_end = $row->update_grace_period_end_date . ' ' . $row->update_grace_period_end_time;
|
$update_end = $row->update_grace_period_end_date . ' ' . $row->update_grace_period_end_time;
|
||||||
$is_update_period = ($now >= $update_start && $now <= $update_end);
|
$is_update_period = ($now >= $update_start && $now <= $update_end);
|
||||||
|
|
||||||
// ボタン表示有無判定
|
// ゾーンマスタの件数分だけループする
|
||||||
$vacant = ($vacantInfo ? $vacantInfo->vacant : 0) - $reservedCount;
|
$zone_data = [];
|
||||||
if ($vacant > 0 && $is_update_period) { // 定期契約ボタン (空き台数が1台以上かつ更新期間内)
|
foreach ($zoneInfo as $zone) {
|
||||||
|
|
||||||
|
// 予約中件数取得
|
||||||
|
$reservedCount = \DB::table('reserve')
|
||||||
|
->where('park_id', $row->park_id)
|
||||||
|
->where('psection_id', $zone->psection_id)
|
||||||
|
->where('ptype_id', $zone->ptype_id)
|
||||||
|
->where('valid_flag', 1)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// ステータス(表示ボタン)判定
|
||||||
|
$status = 0; // 0:非表示, 1:定期契約, 2:空き待ち予約, 3:販売期間外
|
||||||
|
$zone_vacant = $zone->zone_tolerance - $zone->zone_number - $reservedCount;
|
||||||
|
if ($zone_vacant > 0 && $is_update_period) { // 定期契約ボタン (空き台数が1台以上かつ更新期間内)
|
||||||
$status = 1;
|
$status = 1;
|
||||||
} elseif ($vacant <= 0 && $is_update_period) { // 空き待ち予約ボタン (空き台数が0台以下かつ更新期間内)
|
} elseif ($zone_vacant <= 0 && $is_update_period) { // 空き待ち予約ボタン (空き台数が0台以下かつ更新期間内)
|
||||||
$status = 2;
|
$status = 2;
|
||||||
} elseif ($vacant <= 0 && !$is_update_period) { // 販売期間外ボタン (空き台数が0台以下かつ更新期間外)
|
} elseif ($zone_vacant <= 0 && !$is_update_period) { // 販売期間外ボタン (空き台数が0台以下かつ更新期間外)
|
||||||
$status = 3;
|
$status = 3;
|
||||||
} else {
|
|
||||||
$status = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返却用データに追加
|
||||||
|
$zone_data[] = [
|
||||||
|
'psection_subject' => $zone->psection_subject,
|
||||||
|
'ptype_subject' => $zone->ptype_subject,
|
||||||
|
'zone_standard' => $zone->zone_standard,
|
||||||
|
'zone_vacant' => $zone_vacant,
|
||||||
|
'status' => $status
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// $zone_dataを並び替え
|
||||||
|
usort($zone_data, function($a, $b) {
|
||||||
|
// 第一優先: ptype_subject昇順
|
||||||
|
$ptypeCmp = strcmp($a['ptype_subject'], $b['ptype_subject']);
|
||||||
|
if ($ptypeCmp !== 0) {
|
||||||
|
return $ptypeCmp;
|
||||||
|
}
|
||||||
|
// 第二優先: psection_subject昇順
|
||||||
|
$psectionCmp = strcmp($a['psection_subject'], $b['psection_subject']);
|
||||||
|
if ($psectionCmp !== 0) {
|
||||||
|
return $psectionCmp;
|
||||||
|
}
|
||||||
|
// 第三優先: status昇順
|
||||||
|
return $a['status'] <=> $b['status'];
|
||||||
|
});
|
||||||
|
|
||||||
// 画面返却用データに追加
|
// 画面返却用データに追加
|
||||||
$form_data[] = [
|
$form_data[] = [
|
||||||
'park_name' => $row->park_name,
|
'park_name' => $row->park_name,
|
||||||
|
'park_adrs' => $row->park_adrs,
|
||||||
|
'price_memo' => $row->price_memo,
|
||||||
|
'park_latitude' => $row->park_latitude,
|
||||||
|
'park_longitude' => $row->park_longitude,
|
||||||
'city_name' => $row->city_name,
|
'city_name' => $row->city_name,
|
||||||
'station_neighbor_station' => $row->station_neighbor_station,
|
'station_neighbor_station' => $row->station_neighbor_station,
|
||||||
'status' => $status
|
'zone_data' => $zone_data
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +186,10 @@ class ParkingSearchController extends Controller
|
|||||||
'わ行'=>'わをんワヲン '
|
'わ行'=>'わをんワヲン '
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 車種区分リスト取得
|
||||||
|
$psections = \DB::table('psection')->select('psection_subject')->orderBy('psection_id', 'asc')->limit(4)->get();
|
||||||
|
|
||||||
// 情報返却
|
// 情報返却
|
||||||
return ['form_data' => $form_data, 'cities' => $cities, 'stations' => $stations, 'parks' => $parks, 'conditions' => $conditions];
|
return ['form_data' => $form_data, 'conditions' => $conditions, 'cities' => $cities, 'stations' => $stations, 'parks' => $parks, 'psections' => $psections];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<h3 class="other mt50">開示等の依頼の手続き、使用する様式</h3>
|
<h3 class="other mt50">開示等の依頼の手続き、使用する様式</h3>
|
||||||
<p class="p1">開示等の依頼は、以下の手続き及び様式に則って実施致します。</p>
|
<p class="p1">開示等の依頼は、以下の手続き及び様式に則って実施致します。</p>
|
||||||
<p class="p1">利用目的の通知:本書面の“開示対象個人情報の利用目的”をご覧下さい。</p>
|
<p class="p1">利用目的の通知:本書面の“開示対象個人情報の利用目的”をご覧下さい。</p>
|
||||||
<p class="p1">開示、訂正・削除、利用停止:当社の定める様式にて実施致します。該当の受付け窓口にご連絡頂き、所定の様式『<a href="{{ asset('assets/page-img/privacy_disclosure.pdf') }}" target="_blank">個人情報開示等依頼書(PDF:9KB)</a>』を入手のうえ、手続きをお願い致します。</p>
|
<p class="p1">開示、訂正・削除、利用停止:当社の定める様式にて実施致します。該当の受付け窓口にご連絡頂き、所定の様式『<a href="{{ asset('assets/privacy_disclosure.pdf') }}" target="_blank">個人情報開示等依頼書(PDF:9KB)</a>』を入手のうえ、手続きをお願い致します。</p>
|
||||||
<p class="p1">回答に関しては、記入済み個人情報開示等依頼書を、ご自宅への郵送のみとさせて頂きます。</p>
|
<p class="p1">回答に関しては、記入済み個人情報開示等依頼書を、ご自宅への郵送のみとさせて頂きます。</p>
|
||||||
<h3 class="other mt50">開示対象個人情報の取扱いに関する苦情受付け窓口</h3>
|
<h3 class="other mt50">開示対象個人情報の取扱いに関する苦情受付け窓口</h3>
|
||||||
<p class="p1">開示対象個人情報の取扱いに関する苦情、相談に関しましては、以下の窓口宛てにご連絡ください。</p>
|
<p class="p1">開示対象個人情報の取扱いに関する苦情、相談に関しましては、以下の窓口宛てにご連絡ください。</p>
|
||||||
|
|||||||
@ -65,10 +65,12 @@
|
|||||||
<table id="searchTable" class="tablesorter table table-striped">
|
<table id="searchTable" class="tablesorter table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>駐輪場名</th>
|
<th width="20%">駐輪場名</th>
|
||||||
<th>市町村名</th>
|
<th width="15%">市町村名</th>
|
||||||
<th>駅名</th>
|
<th width="5%">駅名</th>
|
||||||
<th></th>
|
@foreach($psections as $psection)
|
||||||
|
<th width="15%">{{ $psection->psection_subject }}</th>
|
||||||
|
@endforeach
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -82,18 +84,39 @@
|
|||||||
@endphp
|
@endphp
|
||||||
@foreach($pagedData as $data)
|
@foreach($pagedData as $data)
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="#placeModal" data-toggle="modal" data-target="#placeModal">{{ $data['park_name'] }}</a></td>
|
<td>
|
||||||
|
<a href="#placeModal"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#placeModal"
|
||||||
|
data-park_name="{{ $data['park_name'] }}"
|
||||||
|
data-park_adrs="{{ $data['park_adrs'] ?? '' }}"
|
||||||
|
data-price_memo="{{ $data['price_memo'] ?? '' }}"
|
||||||
|
data-park_latitude="{{ $data['park_latitude'] ?? '' }}"
|
||||||
|
data-park_longitude="{{ $data['park_longitude'] ?? '' }}"
|
||||||
|
data-city_name="{{ $data['city_name'] }}"
|
||||||
|
data-station="{{ $data['station_neighbor_station'] }}"
|
||||||
|
data-zone_data='@json($data["zone_data"])'>
|
||||||
|
{{ $data['park_name'] }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td>{{ $data['city_name'] }}</td>
|
<td>{{ $data['city_name'] }}</td>
|
||||||
<td>{{ $data['station_neighbor_station'] }}</td>
|
<td>{{ $data['station_neighbor_station'] }}</td>
|
||||||
|
@foreach($psections as $psection)
|
||||||
<td>
|
<td>
|
||||||
@if($data['status'] == 1)
|
@foreach($data['zone_data'] as $zone)
|
||||||
|
@if($zone['psection_subject'] == $psection->psection_subject)
|
||||||
|
@if($zone['status'] == 1)
|
||||||
<a href="{{route('user.info')}}" class="btn btn-block btn-sm btn-outline-success">定期契約</a>
|
<a href="{{route('user.info')}}" class="btn btn-block btn-sm btn-outline-success">定期契約</a>
|
||||||
@elseif($data['status'] == 2)
|
@elseif($zone['status'] == 2)
|
||||||
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-outline-primary">空き待ち予約</a>
|
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-outline-primary">空き待ち予約</a>
|
||||||
@elseif($data['status'] == 3)
|
@elseif($zone['status'] == 3)
|
||||||
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-secondary">販売期間外</a>
|
<a href="{{route('park_waitlist.index')}}" class="btn btn-block btn-sm btn-secondary">販売期間外</a>
|
||||||
@endif
|
@endif
|
||||||
|
@break;
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
</td>
|
</td>
|
||||||
|
@endforeach
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -133,29 +156,86 @@
|
|||||||
<div class="modal fade" id="placeModal" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="modal fade" id="placeModal" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="modalParkName">駐輪場名</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる"><span aria-hidden="true">×</span></button>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
|
||||||
$('#placeModal').on('show.bs.modal', function (event) {
|
$('#placeModal').on('show.bs.modal', function (event) {
|
||||||
var button = $(event.relatedTarget);
|
var button = $(event.relatedTarget);
|
||||||
var parkName = button.text();
|
var parkName = button.data('park_name') || '';
|
||||||
$('#modalParkName').text(parkName);
|
var parkAdrs = button.data('park_adrs') || '';
|
||||||
|
var parkMemo = button.data('price_memo') || '';
|
||||||
|
var lat = button.data('park_latitude') || '';
|
||||||
|
var lng = button.data('park_longitude') || '';
|
||||||
|
$('#parkName').text(parkName).attr('data-park_name', parkName);
|
||||||
|
$('#parkAdrs').text('住所:' + parkAdrs).attr('data-park_adrs', parkAdrs);
|
||||||
|
$('#parkmemo').text(parkMemo).attr('data-price_memo', parkMemo);
|
||||||
|
$('#parkMap').attr('src', 'https://www.google.com/maps?q=' + lat + ',' + lng + '&z=15&output=embed');
|
||||||
|
|
||||||
|
// zone_dataはJSON文字列として渡されているのでパース
|
||||||
|
var zoneData = button.data('zone_data') || [];
|
||||||
|
|
||||||
|
// psection_subjectごとの標準収容台数を集計
|
||||||
|
var standardMap = {};
|
||||||
|
if (zoneData && Array.isArray(zoneData)) {
|
||||||
|
zoneData.forEach(function(zone) {
|
||||||
|
if (!standardMap[zone.psection_subject]) {
|
||||||
|
standardMap[zone.psection_subject] = 0;
|
||||||
|
}
|
||||||
|
standardMap[zone.psection_subject] += parseInt(zone.zone_standard, 10) || 0;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
// 表示用文字列を生成
|
||||||
|
var standardText = '';
|
||||||
|
var keys = Object.keys(standardMap);
|
||||||
|
if (keys.length > 0) {
|
||||||
|
standardText = '【標準収容台数】';
|
||||||
|
standardText += keys.map(function(key) {
|
||||||
|
return key + ':' + standardMap[key] + '台';
|
||||||
|
}).join(' / ');
|
||||||
|
}
|
||||||
|
$('#parkStandard').text(standardText);
|
||||||
|
|
||||||
|
// 各ゾーンの空き台数・ボタンを表示
|
||||||
|
var html = '';
|
||||||
|
if (zoneData && Array.isArray(zoneData)) {
|
||||||
|
var grouped = {};
|
||||||
|
zoneData.forEach(function(zone) {
|
||||||
|
if (!grouped[zone.ptype_subject]) grouped[zone.ptype_subject] = [];
|
||||||
|
grouped[zone.ptype_subject].push(zone);
|
||||||
|
});
|
||||||
|
Object.keys(grouped).forEach(function(ptype) {
|
||||||
|
html += '<h4 class="mt-3">' + ptype + '</h4>';
|
||||||
|
grouped[ptype].forEach(function(zone) {
|
||||||
|
html += '<div class="d-flex align-items-center mb-2">';
|
||||||
|
html += '<span>' + zone.psection_subject + ':空き' + zone.zone_vacant + '台</span>';
|
||||||
|
if (zone.status == 1) {
|
||||||
|
html += '<a href="{{route('user.info')}}" class="btn btn-sm btn-outline-success ml-2">定期契約</a>';
|
||||||
|
} else if (zone.status == 2) {
|
||||||
|
html += '<a href="{{route('park_waitlist.index')}}" class="btn btn-sm btn-outline-primary ml-2">空き待ち予約</a>';
|
||||||
|
} else if (zone.status == 3) {
|
||||||
|
html += '<a href="{{route('park_waitlist.index')}}" class="btn btn-sm btn-outline-secondary ml-2">販売期間外</a>';
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$('#zoneData').html(html);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="parkName" data-park_name=""></h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="閉じる"><span aria-hidden="true">×</span></button>
|
||||||
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3240.722943699139!2d139.75162621525894!3d35.68382338019366!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60188c0b185b3b75%3A0x3282e79fbc91959c!2z44CSMTAwLTAwMDEg5p2x5Lqs6YO95Y2D5Luj55Sw5Yy65Y2D5Luj55Sw77yR4oiS77yR!5e0!3m2!1sja!2sjp!4v1536695351221" width="100%" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
|
<iframe id="parkMap" src="" width="100%" height="450" frameborder="0" style="border:0" allowfullscreen></iframe>
|
||||||
<p class="small">〒000-0000 東京都千代田区1-1 <br class="sp">標準収容台数:XXX台</p>
|
<p class="small">
|
||||||
<p class="text-danger">空き台数:XXX台</p>
|
<span id="parkAdrs"> </span>
|
||||||
|
<span id="parkStandard"></span><br />
|
||||||
|
<span id="parkmemo"></span>
|
||||||
|
</p>
|
||||||
|
<span id="zoneData"></span>
|
||||||
|
<div class="text-right"><button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer"></div>
|
||||||
<button type="submit" class="btn btn-success" onClick="location.href='./SWC-08-02.html'">定期契約</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">閉じる</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
Loading…
Reference in New Issue
Block a user