u
This commit is contained in:
@@ -4,12 +4,13 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:label="Car Maintenance"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:roundIcon="@drawable/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/Theme.CarMaintenance">
|
||||
<activity android:name=".CustomerAppointmentActivity" />
|
||||
<activity android:name=".RoleHomeActivity" />
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
|
||||
@@ -7,4 +7,19 @@ import retrofit2.http.POST;
|
||||
public interface ApiService {
|
||||
@POST("auth/login")
|
||||
Call<ApiResult<LoginResponse>> login(@Body LoginRequest request);
|
||||
|
||||
@retrofit2.http.GET("customers/user/{userId}")
|
||||
Call<ApiResult<Customer>> getCustomerByUserId(@retrofit2.http.Path("userId") int userId);
|
||||
|
||||
@retrofit2.http.GET("vehicles/customer/{customerId}")
|
||||
Call<ApiResult<java.util.List<Vehicle>>> getVehiclesByCustomerId(@retrofit2.http.Path("customerId") int customerId);
|
||||
|
||||
@retrofit2.http.GET("appointments/customer/{customerId}")
|
||||
Call<ApiResult<java.util.List<Appointment>>> getAppointmentsByCustomerId(@retrofit2.http.Path("customerId") int customerId);
|
||||
|
||||
@retrofit2.http.POST("appointments")
|
||||
Call<ApiResult<Appointment>> createAppointment(@retrofit2.http.Body AppointmentCreateRequest request);
|
||||
|
||||
@retrofit2.http.PUT("appointments/{id}/cancel")
|
||||
Call<ApiResult<Appointment>> cancelAppointment(@retrofit2.http.Path("id") int id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
public class Appointment {
|
||||
private Integer appointmentId;
|
||||
private Integer customerId;
|
||||
private Integer vehicleId;
|
||||
private String serviceType;
|
||||
private String appointmentTime;
|
||||
private String contactPhone;
|
||||
private String description;
|
||||
private String status;
|
||||
|
||||
public Integer getAppointmentId() {
|
||||
return appointmentId;
|
||||
}
|
||||
|
||||
public Integer getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public Integer getVehicleId() {
|
||||
return vehicleId;
|
||||
}
|
||||
|
||||
public String getServiceType() {
|
||||
return serviceType;
|
||||
}
|
||||
|
||||
public String getAppointmentTime() {
|
||||
return appointmentTime;
|
||||
}
|
||||
|
||||
public String getContactPhone() {
|
||||
return contactPhone;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.ViewHolder> {
|
||||
public interface OnCancelListener {
|
||||
void onCancel(Appointment appointment);
|
||||
}
|
||||
|
||||
private final List<Appointment> items = new ArrayList<>();
|
||||
private final Map<Integer, Vehicle> vehicleMap;
|
||||
private final OnCancelListener cancelListener;
|
||||
|
||||
public AppointmentAdapter(Map<Integer, Vehicle> vehicleMap, OnCancelListener cancelListener) {
|
||||
this.vehicleMap = vehicleMap;
|
||||
this.cancelListener = cancelListener;
|
||||
}
|
||||
|
||||
public void setItems(List<Appointment> appointments) {
|
||||
items.clear();
|
||||
if (appointments != null) {
|
||||
items.addAll(appointments);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_appointment, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Appointment appointment = items.get(position);
|
||||
Vehicle vehicle = vehicleMap.get(appointment.getVehicleId());
|
||||
String plate = vehicle != null ? vehicle.getLicensePlate() : "-";
|
||||
|
||||
holder.summary.setText(ServiceTypeMapper.toDisplay(appointment.getServiceType()) + " - " + plate);
|
||||
holder.detail.setText(holder.itemView.getContext().getString(R.string.appointment_time_label, appointment.getAppointmentTime()));
|
||||
holder.status.setText(ServiceTypeMapper.statusDisplay(appointment.getStatus()));
|
||||
|
||||
boolean canCancel = "pending".equalsIgnoreCase(appointment.getStatus());
|
||||
holder.cancelButton.setVisibility(canCancel ? View.VISIBLE : View.GONE);
|
||||
holder.cancelButton.setOnClickListener(v -> cancelListener.onCancel(appointment));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView summary;
|
||||
final TextView detail;
|
||||
final TextView status;
|
||||
final Button cancelButton;
|
||||
|
||||
ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
summary = itemView.findViewById(R.id.appointmentSummary);
|
||||
detail = itemView.findViewById(R.id.appointmentDetail);
|
||||
status = itemView.findViewById(R.id.appointmentStatus);
|
||||
cancelButton = itemView.findViewById(R.id.cancelAppointmentButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
public class AppointmentCreateRequest {
|
||||
private final Integer customerId;
|
||||
private final Integer vehicleId;
|
||||
private final String serviceType;
|
||||
private final String appointmentTime;
|
||||
private final String contactPhone;
|
||||
private final String description;
|
||||
|
||||
public AppointmentCreateRequest(Integer customerId, Integer vehicleId, String serviceType,
|
||||
String appointmentTime, String contactPhone, String description) {
|
||||
this.customerId = customerId;
|
||||
this.vehicleId = vehicleId;
|
||||
this.serviceType = serviceType;
|
||||
this.appointmentTime = appointmentTime;
|
||||
this.contactPhone = contactPhone;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
24
android/app/src/main/java/com/carmaintenance/Customer.java
Normal file
24
android/app/src/main/java/com/carmaintenance/Customer.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
public class Customer {
|
||||
private Integer customerId;
|
||||
private Integer userId;
|
||||
private String realName;
|
||||
private String phone;
|
||||
|
||||
public Integer getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public Integer getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getRealName() {
|
||||
return realName;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.app.TimePickerDialog;
|
||||
import android.widget.Button;
|
||||
import android.widget.DatePicker;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TimePicker;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class CustomerAppointmentActivity extends AppCompatActivity {
|
||||
private Spinner vehicleSpinner;
|
||||
private Spinner serviceTypeSpinner;
|
||||
private EditText appointmentTimeInput;
|
||||
private EditText contactPhoneInput;
|
||||
private EditText descriptionInput;
|
||||
private ProgressBar progressBar;
|
||||
private AppointmentAdapter adapter;
|
||||
private final Calendar appointmentCalendar = Calendar.getInstance();
|
||||
private final SimpleDateFormat appointmentFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.CHINA);
|
||||
|
||||
private final List<Vehicle> vehicles = new ArrayList<>();
|
||||
private final Map<Integer, Vehicle> vehicleMap = new HashMap<>();
|
||||
private Integer customerId;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_customer_appointment);
|
||||
|
||||
vehicleSpinner = findViewById(R.id.vehicleSpinner);
|
||||
serviceTypeSpinner = findViewById(R.id.serviceTypeSpinner);
|
||||
appointmentTimeInput = findViewById(R.id.appointmentTimeInput);
|
||||
contactPhoneInput = findViewById(R.id.contactPhoneInput);
|
||||
descriptionInput = findViewById(R.id.descriptionInput);
|
||||
progressBar = findViewById(R.id.appointmentProgress);
|
||||
Button submitButton = findViewById(R.id.submitAppointmentButton);
|
||||
RecyclerView recyclerView = findViewById(R.id.appointmentRecycler);
|
||||
|
||||
adapter = new AppointmentAdapter(vehicleMap, this::cancelAppointment);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
serviceTypeSpinner.setAdapter(new ArrayAdapter<>(
|
||||
this,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
ServiceTypeMapperOptions.getServiceTypes()
|
||||
));
|
||||
|
||||
appointmentTimeInput.setOnClickListener(v -> openDatePicker());
|
||||
appointmentTimeInput.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
openDatePicker();
|
||||
}
|
||||
});
|
||||
|
||||
submitButton.setOnClickListener(v -> submitAppointment());
|
||||
|
||||
loadCustomerContext();
|
||||
}
|
||||
|
||||
private void loadCustomerContext() {
|
||||
setLoading(true);
|
||||
int userId = new UserSession(this).getUserId();
|
||||
if (userId == 0) {
|
||||
toast(R.string.error_missing_user);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ApiClient.getService().getCustomerByUserId(userId).enqueue(new Callback<ApiResult<Customer>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResult<Customer>> call, Response<ApiResult<Customer>> response) {
|
||||
if (!response.isSuccessful() || response.body() == null || response.body().getData() == null) {
|
||||
toast(R.string.error_load_customer);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
customerId = response.body().getData().getCustomerId();
|
||||
loadVehiclesAndAppointments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<Customer>> call, Throwable t) {
|
||||
toast(getString(R.string.error_network) + t.getMessage());
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadVehiclesAndAppointments() {
|
||||
if (customerId == null) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ApiClient.getService().getVehiclesByCustomerId(customerId).enqueue(new Callback<ApiResult<List<Vehicle>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResult<List<Vehicle>>> call, Response<ApiResult<List<Vehicle>>> response) {
|
||||
vehicles.clear();
|
||||
vehicleMap.clear();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getData() != null) {
|
||||
vehicles.addAll(response.body().getData());
|
||||
}
|
||||
|
||||
for (Vehicle v : vehicles) {
|
||||
if (v.getVehicleId() != null) {
|
||||
vehicleMap.put(v.getVehicleId(), v);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> labels = new ArrayList<>();
|
||||
labels.add(getString(R.string.select_vehicle));
|
||||
for (Vehicle v : vehicles) {
|
||||
labels.add(v.getLicensePlate() + " - " + v.getBrand() + " " + v.getModel());
|
||||
}
|
||||
vehicleSpinner.setAdapter(new ArrayAdapter<>(
|
||||
CustomerAppointmentActivity.this,
|
||||
android.R.layout.simple_spinner_dropdown_item,
|
||||
labels
|
||||
));
|
||||
|
||||
loadAppointments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<List<Vehicle>>> call, Throwable t) {
|
||||
toast(getString(R.string.error_network) + t.getMessage());
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadAppointments() {
|
||||
if (customerId == null) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
ApiClient.getService().getAppointmentsByCustomerId(customerId).enqueue(new Callback<ApiResult<List<Appointment>>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResult<List<Appointment>>> call, Response<ApiResult<List<Appointment>>> response) {
|
||||
setLoading(false);
|
||||
if (!response.isSuccessful() || response.body() == null) {
|
||||
toast(R.string.error_load_appointments);
|
||||
return;
|
||||
}
|
||||
adapter.setItems(response.body().getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<List<Appointment>>> call, Throwable t) {
|
||||
setLoading(false);
|
||||
toast(getString(R.string.error_network) + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void submitAppointment() {
|
||||
if (customerId == null) {
|
||||
toast(R.string.error_load_customer);
|
||||
return;
|
||||
}
|
||||
if (vehicleSpinner.getSelectedItemPosition() == 0) {
|
||||
toast(R.string.error_select_vehicle);
|
||||
return;
|
||||
}
|
||||
|
||||
String appointmentTime = appointmentTimeInput.getText() != null ? appointmentTimeInput.getText().toString().trim() : "";
|
||||
String contactPhone = contactPhoneInput.getText() != null ? contactPhoneInput.getText().toString().trim() : "";
|
||||
String description = descriptionInput.getText() != null ? descriptionInput.getText().toString().trim() : "";
|
||||
|
||||
if (TextUtils.isEmpty(appointmentTime)) {
|
||||
toast(R.string.error_select_time);
|
||||
return;
|
||||
}
|
||||
if (TextUtils.isEmpty(contactPhone)) {
|
||||
toast(R.string.error_contact_phone);
|
||||
return;
|
||||
}
|
||||
|
||||
Vehicle selectedVehicle = vehicles.get(vehicleSpinner.getSelectedItemPosition() - 1);
|
||||
String serviceType = ServiceTypeMapperOptions.getServiceTypeValue(serviceTypeSpinner.getSelectedItemPosition());
|
||||
|
||||
setLoading(true);
|
||||
AppointmentCreateRequest request = new AppointmentCreateRequest(
|
||||
customerId,
|
||||
selectedVehicle.getVehicleId(),
|
||||
serviceType,
|
||||
appointmentTime,
|
||||
contactPhone,
|
||||
description
|
||||
);
|
||||
|
||||
ApiClient.getService().createAppointment(request).enqueue(new Callback<ApiResult<Appointment>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResult<Appointment>> call, Response<ApiResult<Appointment>> response) {
|
||||
setLoading(false);
|
||||
if (!response.isSuccessful() || response.body() == null || response.body().getCode() != 200) {
|
||||
toast(R.string.error_submit_appointment);
|
||||
return;
|
||||
}
|
||||
appointmentTimeInput.setText("");
|
||||
contactPhoneInput.setText("");
|
||||
descriptionInput.setText("");
|
||||
toast(R.string.success_submit_appointment);
|
||||
loadAppointments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<Appointment>> call, Throwable t) {
|
||||
setLoading(false);
|
||||
toast(getString(R.string.error_network) + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cancelAppointment(Appointment appointment) {
|
||||
if (appointment.getAppointmentId() == null) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
ApiClient.getService().cancelAppointment(appointment.getAppointmentId()).enqueue(new Callback<ApiResult<Appointment>>() {
|
||||
@Override
|
||||
public void onResponse(Call<ApiResult<Appointment>> call, Response<ApiResult<Appointment>> response) {
|
||||
setLoading(false);
|
||||
if (!response.isSuccessful() || response.body() == null || response.body().getCode() != 200) {
|
||||
toast(R.string.error_cancel_appointment);
|
||||
return;
|
||||
}
|
||||
toast(R.string.success_cancel_appointment);
|
||||
loadAppointments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<Appointment>> call, Throwable t) {
|
||||
setLoading(false);
|
||||
toast(getString(R.string.error_network) + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void openDatePicker() {
|
||||
DatePickerDialog dialog = new DatePickerDialog(
|
||||
this,
|
||||
(DatePicker view, int year, int month, int dayOfMonth) -> {
|
||||
appointmentCalendar.set(Calendar.YEAR, year);
|
||||
appointmentCalendar.set(Calendar.MONTH, month);
|
||||
appointmentCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
|
||||
openTimePicker();
|
||||
},
|
||||
appointmentCalendar.get(Calendar.YEAR),
|
||||
appointmentCalendar.get(Calendar.MONTH),
|
||||
appointmentCalendar.get(Calendar.DAY_OF_MONTH)
|
||||
);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void openTimePicker() {
|
||||
TimePickerDialog dialog = new TimePickerDialog(
|
||||
this,
|
||||
(TimePicker view, int hourOfDay, int minute) -> {
|
||||
appointmentCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||
appointmentCalendar.set(Calendar.MINUTE, minute);
|
||||
appointmentCalendar.set(Calendar.SECOND, 0);
|
||||
appointmentTimeInput.setText(appointmentFormat.format(appointmentCalendar.getTime()));
|
||||
},
|
||||
appointmentCalendar.get(Calendar.HOUR_OF_DAY),
|
||||
appointmentCalendar.get(Calendar.MINUTE),
|
||||
true
|
||||
);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void toast(int resId) {
|
||||
Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void toast(String text) {
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||
String password = textOf(passwordInput);
|
||||
|
||||
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
|
||||
showError("Username and password are required.");
|
||||
showError(getString(R.string.error_required));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,13 @@ public class LoginActivity extends AppCompatActivity {
|
||||
setLoading(false);
|
||||
|
||||
if (!response.isSuccessful() || response.body() == null) {
|
||||
showError("Login failed. Please try again.");
|
||||
showError(getString(R.string.error_login_failed));
|
||||
return;
|
||||
}
|
||||
|
||||
ApiResult<LoginResponse> result = response.body();
|
||||
if (result.getCode() != 200 || result.getData() == null) {
|
||||
showError(result.getMessage() != null ? result.getMessage() : "Login failed.");
|
||||
showError(result.getMessage() != null ? result.getMessage() : getString(R.string.error_login_failed));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ public class LoginActivity extends AppCompatActivity {
|
||||
|
||||
String role = data.getUserInfo() != null ? data.getUserInfo().getRole() : "unknown";
|
||||
String displayName = data.getUserInfo() != null ? data.getUserInfo().getUsername() : username;
|
||||
int userId = data.getUserInfo() != null && data.getUserInfo().getUserId() != null
|
||||
? data.getUserInfo().getUserId()
|
||||
: 0;
|
||||
new UserSession(LoginActivity.this).save(userId, displayName, role);
|
||||
|
||||
Intent intent = new Intent(LoginActivity.this, RoleHomeActivity.class);
|
||||
intent.putExtra("role", role);
|
||||
@@ -80,7 +84,7 @@ public class LoginActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onFailure(Call<ApiResult<LoginResponse>> call, Throwable t) {
|
||||
setLoading(false);
|
||||
showError("Network error: " + t.getMessage());
|
||||
showError(getString(R.string.error_network) + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -28,24 +28,36 @@ public class RoleHomeActivity extends AppCompatActivity {
|
||||
username = "user";
|
||||
}
|
||||
|
||||
userInfo.setText("Welcome " + username + " (" + role + ")");
|
||||
userInfo.setText(getString(R.string.welcome_format, username, role));
|
||||
|
||||
if ("admin".equalsIgnoreCase(role)) {
|
||||
primaryAction.setText("Manage Users");
|
||||
secondaryAction.setText("Manage Orders");
|
||||
primaryAction.setText(R.string.role_admin_primary);
|
||||
secondaryAction.setText(R.string.role_admin_secondary);
|
||||
primaryAction.setOnClickListener(v -> {});
|
||||
secondaryAction.setOnClickListener(v -> {});
|
||||
} else if ("staff".equalsIgnoreCase(role)) {
|
||||
primaryAction.setText("Search Vehicles");
|
||||
secondaryAction.setText("Parts Lookup");
|
||||
primaryAction.setText(R.string.role_staff_primary);
|
||||
secondaryAction.setText(R.string.role_staff_secondary);
|
||||
primaryAction.setOnClickListener(v -> {});
|
||||
secondaryAction.setOnClickListener(v -> {});
|
||||
} else {
|
||||
primaryAction.setText("My Vehicles");
|
||||
secondaryAction.setText("My Appointments");
|
||||
primaryAction.setText(R.string.role_customer_primary);
|
||||
secondaryAction.setText(R.string.role_customer_secondary);
|
||||
primaryAction.setOnClickListener(v -> openCustomerAppointment());
|
||||
secondaryAction.setOnClickListener(v -> openCustomerAppointment());
|
||||
}
|
||||
|
||||
logoutButton.setOnClickListener(v -> {
|
||||
new TokenStore(RoleHomeActivity.this).clear();
|
||||
new UserSession(RoleHomeActivity.this).clear();
|
||||
Intent intent = new Intent(RoleHomeActivity.this, LoginActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
|
||||
private void openCustomerAppointment() {
|
||||
Intent intent = new Intent(RoleHomeActivity.this, CustomerAppointmentActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
public class ServiceTypeMapper {
|
||||
public static String toDisplay(String serviceType) {
|
||||
if (serviceType == null) {
|
||||
return "未知服务";
|
||||
}
|
||||
switch (serviceType) {
|
||||
case "maintenance":
|
||||
return "保养";
|
||||
case "repair":
|
||||
return "维修";
|
||||
case "beauty":
|
||||
return "美容";
|
||||
case "insurance":
|
||||
return "保险";
|
||||
default:
|
||||
return serviceType;
|
||||
}
|
||||
}
|
||||
|
||||
public static String statusDisplay(String status) {
|
||||
if (status == null) {
|
||||
return "状态:未知";
|
||||
}
|
||||
switch (status) {
|
||||
case "pending":
|
||||
return "状态:待确认";
|
||||
case "confirmed":
|
||||
return "状态:已确认";
|
||||
case "completed":
|
||||
return "状态:已完成";
|
||||
case "cancelled":
|
||||
return "状态:已取消";
|
||||
default:
|
||||
return "状态:" + status;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceTypeMapperOptions {
|
||||
public static List<String> getServiceTypes() {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("保养");
|
||||
list.add("维修");
|
||||
list.add("美容");
|
||||
list.add("保险");
|
||||
return list;
|
||||
}
|
||||
|
||||
public static String getServiceTypeValue(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return "maintenance";
|
||||
case 1:
|
||||
return "repair";
|
||||
case 2:
|
||||
return "beauty";
|
||||
case 3:
|
||||
return "insurance";
|
||||
default:
|
||||
return "maintenance";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class UserSession {
|
||||
private static final String PREFS_NAME = "auth_prefs";
|
||||
private static final String KEY_USER_ID = "user_id";
|
||||
private static final String KEY_USERNAME = "username";
|
||||
private static final String KEY_ROLE = "role";
|
||||
|
||||
private final SharedPreferences preferences;
|
||||
|
||||
public UserSession(Context context) {
|
||||
this.preferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public void save(int userId, String username, String role) {
|
||||
preferences.edit()
|
||||
.putInt(KEY_USER_ID, userId)
|
||||
.putString(KEY_USERNAME, username)
|
||||
.putString(KEY_ROLE, role)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return preferences.getInt(KEY_USER_ID, 0);
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return preferences.getString(KEY_USERNAME, "");
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return preferences.getString(KEY_ROLE, "");
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
preferences.edit()
|
||||
.remove(KEY_USER_ID)
|
||||
.remove(KEY_USERNAME)
|
||||
.remove(KEY_ROLE)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
24
android/app/src/main/java/com/carmaintenance/Vehicle.java
Normal file
24
android/app/src/main/java/com/carmaintenance/Vehicle.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.carmaintenance;
|
||||
|
||||
public class Vehicle {
|
||||
private Integer vehicleId;
|
||||
private String licensePlate;
|
||||
private String brand;
|
||||
private String model;
|
||||
|
||||
public Integer getVehicleId() {
|
||||
return vehicleId;
|
||||
}
|
||||
|
||||
public String getLicensePlate() {
|
||||
return licensePlate;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
return brand;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/appointment_title"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/appointment_form_title"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/vehicleSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/serviceTypeSpinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/appointmentTimeInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/appointment_time_hint"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contactPhoneInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/contact_phone"
|
||||
android:inputType="phone" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/descriptionInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/appointment_description"
|
||||
android:minLines="2"
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/submitAppointmentButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/submit_appointment" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/appointmentProgress"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="12dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/appointment_list_title"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/appointmentRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
@@ -10,7 +10,7 @@
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Car Maintenance"
|
||||
android:text="@string/login_title"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
@@ -10,7 +10,7 @@
|
||||
android:id="@+id/welcomeTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Role Home"
|
||||
android:text="@string/welcome_title"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -32,7 +32,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="Primary Action"
|
||||
android:text="@string/primary_action"
|
||||
app:layout_constraintTop_toBottomOf="@id/userInfo"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
@@ -42,7 +42,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Secondary Action"
|
||||
android:text="@string/secondary_action"
|
||||
app:layout_constraintTop_toBottomOf="@id/primaryAction"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
41
android/app/src/main/res/layout/item_appointment.xml
Normal file
41
android/app/src/main/res/layout/item_appointment.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appointmentSummary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appointmentDetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appointmentStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelAppointmentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/cancel_appointment" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -1,8 +1,42 @@
|
||||
<resources>
|
||||
<string name="app_name">Car Maintenance</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="role_home">Role Home</string>
|
||||
<string name="logout">Logout</string>
|
||||
<string name="app_name">车管家</string>
|
||||
<string name="login">登录</string>
|
||||
<string name="username">用户名</string>
|
||||
<string name="password">密码</string>
|
||||
<string name="role_home">角色主页</string>
|
||||
<string name="logout">退出登录</string>
|
||||
<string name="welcome_title">角色主页</string>
|
||||
<string name="login_title">车管家</string>
|
||||
<string name="primary_action">主要功能</string>
|
||||
<string name="secondary_action">次要功能</string>
|
||||
<string name="error_required">请输入用户名和密码</string>
|
||||
<string name="error_login_failed">登录失败,请重试</string>
|
||||
<string name="error_network">网络错误:</string>
|
||||
<string name="role_admin_primary">用户管理</string>
|
||||
<string name="role_admin_secondary">工单管理</string>
|
||||
<string name="role_staff_primary">车辆查询</string>
|
||||
<string name="role_staff_secondary">配件查询</string>
|
||||
<string name="role_customer_primary">我的车辆</string>
|
||||
<string name="role_customer_secondary">在线预约</string>
|
||||
<string name="welcome_format">欢迎 %1$s(%2$s)</string>
|
||||
<string name="appointment_title">在线预约</string>
|
||||
<string name="appointment_form_title">提交预约</string>
|
||||
<string name="appointment_list_title">我的预约</string>
|
||||
<string name="appointment_time_hint">请选择预约时间</string>
|
||||
<string name="contact_phone">联系电话</string>
|
||||
<string name="appointment_description">预约说明</string>
|
||||
<string name="submit_appointment">提交预约</string>
|
||||
<string name="cancel_appointment">取消预约</string>
|
||||
<string name="select_vehicle">请选择车辆</string>
|
||||
<string name="appointment_time_label">预约时间:%1$s</string>
|
||||
<string name="error_missing_user">用户信息缺失,请重新登录</string>
|
||||
<string name="error_load_customer">获取客户信息失败</string>
|
||||
<string name="error_load_appointments">加载预约失败</string>
|
||||
<string name="error_select_vehicle">请选择车辆</string>
|
||||
<string name="error_select_time">请选择预约时间</string>
|
||||
<string name="error_contact_phone">请输入联系电话</string>
|
||||
<string name="error_submit_appointment">预约提交失败</string>
|
||||
<string name="success_submit_appointment">预约成功</string>
|
||||
<string name="error_cancel_appointment">取消预约失败</string>
|
||||
<string name="success_cancel_appointment">预约已取消</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user