feat: extend farmer data model with complete address fields and customer number
Backend: - Add customerNumber, companyName, contactPerson, street, houseNumber, postalCode, city, phoneNumber to Farmer domain model - Update FarmerRow and FarmerMutation records with new fields - Update repositories, services and controllers for new farmer structure - Fix CORS configuration to allow credentials - Remove unused authorizationService and imports - Update data migration for new farmer schema Frontend: - Update FarmerRow and FarmerOption interfaces - Extend AdministrationPage with new farmer form fields - Update SampleRegistrationPage and SearchFarmerPage for new structure
This commit is contained in:
@@ -18,7 +18,8 @@ public class CorsConfig {
|
||||
configuration.setAllowedOrigins(allowedOrigins);
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||
configuration.setAllowCredentials(false);
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setExposedHeaders(List.of("Authorization"));
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/api/**", configuration);
|
||||
|
||||
@@ -10,8 +10,15 @@ public record Farmer(
|
||||
@Id String id,
|
||||
String accountId,
|
||||
String businessKey,
|
||||
String name,
|
||||
String customerNumber,
|
||||
String companyName,
|
||||
String contactPerson,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String email,
|
||||
String phoneNumber,
|
||||
boolean active,
|
||||
String supersedesId,
|
||||
LocalDateTime createdAt,
|
||||
|
||||
@@ -6,11 +6,11 @@ import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import java.util.List;
|
||||
|
||||
public interface FarmerRepository extends MongoRepository<Farmer, String> {
|
||||
List<Farmer> findByActiveTrueOrderByNameAsc();
|
||||
List<Farmer> findByActiveTrueOrderByCompanyNameAsc();
|
||||
|
||||
List<Farmer> findByAccountIdOrderByNameAsc(String accountId);
|
||||
List<Farmer> findByAccountIdOrderByCompanyNameAsc(String accountId);
|
||||
|
||||
List<Farmer> findByAccountIdAndActiveTrueOrderByNameAsc(String accountId);
|
||||
List<Farmer> findByAccountIdAndActiveTrueOrderByCompanyNameAsc(String accountId);
|
||||
|
||||
List<Farmer> findByNameContainingIgnoreCaseOrderByNameAsc(String name);
|
||||
List<Farmer> findByCompanyNameContainingIgnoreCaseOrderByCompanyNameAsc(String companyName);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import de.svencarstensen.muh.repository.FarmerRepository;
|
||||
import de.svencarstensen.muh.repository.MedicationCatalogRepository;
|
||||
import de.svencarstensen.muh.repository.PathogenCatalogRepository;
|
||||
import de.svencarstensen.muh.security.AuthTokenService;
|
||||
import de.svencarstensen.muh.security.AuthorizationService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
@@ -42,7 +41,7 @@ public class CatalogService {
|
||||
|
||||
private static final Comparator<FarmerRow> FARMER_ROW_COMPARATOR = Comparator
|
||||
.comparing(FarmerRow::active).reversed()
|
||||
.thenComparing(FarmerRow::name, String.CASE_INSENSITIVE_ORDER)
|
||||
.thenComparing(FarmerRow::companyName, String.CASE_INSENSITIVE_ORDER)
|
||||
.thenComparing(FarmerRow::updatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
||||
|
||||
private static final Comparator<MedicationRow> MEDICATION_ROW_COMPARATOR = Comparator
|
||||
@@ -67,7 +66,6 @@ public class CatalogService {
|
||||
private final AppUserRepository appUserRepository;
|
||||
private final MongoTemplate mongoTemplate;
|
||||
private final AuthTokenService authTokenService;
|
||||
private final AuthorizationService authorizationService;
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
public CatalogService(
|
||||
@@ -77,8 +75,7 @@ public class CatalogService {
|
||||
AntibioticCatalogRepository antibioticRepository,
|
||||
AppUserRepository appUserRepository,
|
||||
MongoTemplate mongoTemplate,
|
||||
AuthTokenService authTokenService,
|
||||
AuthorizationService authorizationService
|
||||
AuthTokenService authTokenService
|
||||
) {
|
||||
this.farmerRepository = farmerRepository;
|
||||
this.medicationRepository = medicationRepository;
|
||||
@@ -87,7 +84,6 @@ public class CatalogService {
|
||||
this.appUserRepository = appUserRepository;
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
this.authTokenService = authTokenService;
|
||||
this.authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public ActiveCatalogSummary activeCatalogSummary(String actorId) {
|
||||
@@ -113,7 +109,7 @@ public class CatalogService {
|
||||
|
||||
// Hilfsmethoden für Datenzugriff (immer nur eigene Daten des Hauptbenutzers)
|
||||
private List<Farmer> listActiveFarmersForActor(AppUser actor) {
|
||||
return farmerRepository.findByAccountIdAndActiveTrueOrderByNameAsc(resolveAccountId(actor));
|
||||
return farmerRepository.findByAccountIdAndActiveTrueOrderByCompanyNameAsc(resolveAccountId(actor));
|
||||
}
|
||||
|
||||
private List<MedicationCatalogItem> listActiveMedicationsForActor(AppUser actor) {
|
||||
@@ -129,7 +125,7 @@ public class CatalogService {
|
||||
}
|
||||
|
||||
private List<FarmerRow> listFarmerRowsForActor(AppUser actor) {
|
||||
return farmerRepository.findByAccountIdOrderByNameAsc(resolveAccountId(actor)).stream()
|
||||
return farmerRepository.findByAccountIdOrderByCompanyNameAsc(resolveAccountId(actor)).stream()
|
||||
.map(this::toFarmerRow)
|
||||
.sorted(FARMER_ROW_COMPARATOR)
|
||||
.toList();
|
||||
@@ -160,7 +156,7 @@ public class CatalogService {
|
||||
AppUser actor = requireActiveActor(actorId, "Nicht berechtigt");
|
||||
String accountId = resolveAccountId(actor);
|
||||
for (FarmerMutation mutation : mutations) {
|
||||
if (isBlank(mutation.name())) {
|
||||
if (isBlank(mutation.companyName())) {
|
||||
continue;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -169,8 +165,15 @@ public class CatalogService {
|
||||
null,
|
||||
accountId,
|
||||
UUID.randomUUID().toString(),
|
||||
mutation.name().trim(),
|
||||
blankToNull(mutation.customerNumber()),
|
||||
mutation.companyName().trim(),
|
||||
blankToNull(mutation.contactPerson()),
|
||||
blankToNull(mutation.street()),
|
||||
blankToNull(mutation.houseNumber()),
|
||||
blankToNull(mutation.postalCode()),
|
||||
blankToNull(mutation.city()),
|
||||
blankToNull(mutation.email()),
|
||||
blankToNull(mutation.phoneNumber()),
|
||||
mutation.active(),
|
||||
null,
|
||||
now,
|
||||
@@ -185,15 +188,29 @@ public class CatalogService {
|
||||
if (!accountId.equals(existing.accountId())) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Nicht berechtigt");
|
||||
}
|
||||
boolean changed = !existing.name().equals(mutation.name().trim())
|
||||
|| !safeEquals(existing.email(), blankToNull(mutation.email()));
|
||||
boolean changed = !existing.companyName().equals(mutation.companyName().trim())
|
||||
|| !safeEquals(existing.customerNumber(), blankToNull(mutation.customerNumber()))
|
||||
|| !safeEquals(existing.contactPerson(), blankToNull(mutation.contactPerson()))
|
||||
|| !safeEquals(existing.street(), blankToNull(mutation.street()))
|
||||
|| !safeEquals(existing.houseNumber(), blankToNull(mutation.houseNumber()))
|
||||
|| !safeEquals(existing.postalCode(), blankToNull(mutation.postalCode()))
|
||||
|| !safeEquals(existing.city(), blankToNull(mutation.city()))
|
||||
|| !safeEquals(existing.email(), blankToNull(mutation.email()))
|
||||
|| !safeEquals(existing.phoneNumber(), blankToNull(mutation.phoneNumber()));
|
||||
if (changed) {
|
||||
farmerRepository.save(new Farmer(
|
||||
existing.id(),
|
||||
existing.accountId(),
|
||||
existing.businessKey(),
|
||||
existing.name(),
|
||||
existing.customerNumber(),
|
||||
existing.companyName(),
|
||||
existing.contactPerson(),
|
||||
existing.street(),
|
||||
existing.houseNumber(),
|
||||
existing.postalCode(),
|
||||
existing.city(),
|
||||
existing.email(),
|
||||
existing.phoneNumber(),
|
||||
false,
|
||||
existing.supersedesId(),
|
||||
existing.createdAt(),
|
||||
@@ -203,8 +220,15 @@ public class CatalogService {
|
||||
null,
|
||||
existing.accountId(),
|
||||
existing.businessKey(),
|
||||
mutation.name().trim(),
|
||||
blankToNull(mutation.customerNumber()),
|
||||
mutation.companyName().trim(),
|
||||
blankToNull(mutation.contactPerson()),
|
||||
blankToNull(mutation.street()),
|
||||
blankToNull(mutation.houseNumber()),
|
||||
blankToNull(mutation.postalCode()),
|
||||
blankToNull(mutation.city()),
|
||||
blankToNull(mutation.email()),
|
||||
blankToNull(mutation.phoneNumber()),
|
||||
mutation.active(),
|
||||
existing.id(),
|
||||
now,
|
||||
@@ -217,8 +241,15 @@ public class CatalogService {
|
||||
existing.id(),
|
||||
existing.accountId(),
|
||||
existing.businessKey(),
|
||||
existing.name(),
|
||||
existing.customerNumber(),
|
||||
existing.companyName(),
|
||||
existing.contactPerson(),
|
||||
existing.street(),
|
||||
existing.houseNumber(),
|
||||
existing.postalCode(),
|
||||
existing.city(),
|
||||
existing.email(),
|
||||
existing.phoneNumber(),
|
||||
mutation.active(),
|
||||
existing.supersedesId(),
|
||||
existing.createdAt(),
|
||||
@@ -746,8 +777,15 @@ public class CatalogService {
|
||||
return new FarmerRow(
|
||||
farmer.id(),
|
||||
farmer.businessKey(),
|
||||
farmer.name(),
|
||||
farmer.customerNumber(),
|
||||
farmer.companyName(),
|
||||
farmer.contactPerson(),
|
||||
farmer.street(),
|
||||
farmer.houseNumber(),
|
||||
farmer.postalCode(),
|
||||
farmer.city(),
|
||||
farmer.email(),
|
||||
farmer.phoneNumber(),
|
||||
farmer.active(),
|
||||
farmer.updatedAt()
|
||||
);
|
||||
@@ -812,7 +850,7 @@ public class CatalogService {
|
||||
}
|
||||
|
||||
private FarmerOption toFarmerOption(Farmer farmer) {
|
||||
return new FarmerOption(farmer.businessKey(), farmer.name(), farmer.email());
|
||||
return new FarmerOption(farmer.businessKey(), farmer.companyName(), farmer.contactPerson(), farmer.email());
|
||||
}
|
||||
|
||||
private MedicationOption toMedicationOption(MedicationCatalogItem item) {
|
||||
@@ -1077,20 +1115,31 @@ public class CatalogService {
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// Migriere Farmers ohne accountId
|
||||
// Migriere Farmers ohne accountId oder mit altem Schema
|
||||
farmerRepository.findAll().stream()
|
||||
.filter(farmer -> isBlank(farmer.accountId()))
|
||||
.forEach(farmer -> farmerRepository.save(new Farmer(
|
||||
.filter(farmer -> isBlank(farmer.accountId()) || isBlank(farmer.companyName()))
|
||||
.forEach(farmer -> {
|
||||
// Wenn companyName fehlt, nutze businessKey als Fallback
|
||||
String companyName = isBlank(farmer.companyName()) ? farmer.businessKey() : farmer.companyName();
|
||||
farmerRepository.save(new Farmer(
|
||||
farmer.id(),
|
||||
defaultAccountId,
|
||||
isBlank(farmer.accountId()) ? defaultAccountId : farmer.accountId(),
|
||||
farmer.businessKey(),
|
||||
farmer.name(),
|
||||
null, // customerNumber
|
||||
companyName,
|
||||
null, // contactPerson
|
||||
null, // street
|
||||
null, // houseNumber
|
||||
null, // postalCode
|
||||
null, // city
|
||||
farmer.email(),
|
||||
null, // phoneNumber
|
||||
farmer.active(),
|
||||
farmer.supersedesId(),
|
||||
farmer.createdAt(),
|
||||
now
|
||||
)));
|
||||
));
|
||||
});
|
||||
|
||||
// Migriere Medications ohne accountId
|
||||
medicationRepository.findAll().stream()
|
||||
@@ -1213,7 +1262,7 @@ public class CatalogService {
|
||||
) {
|
||||
}
|
||||
|
||||
public record FarmerOption(String businessKey, String name, String email) {
|
||||
public record FarmerOption(String businessKey, String companyName, String contactPerson, String email) {
|
||||
}
|
||||
|
||||
public record MedicationOption(String businessKey, String name, MedicationCategory category) {
|
||||
@@ -1249,14 +1298,33 @@ public class CatalogService {
|
||||
public record FarmerRow(
|
||||
String id,
|
||||
String businessKey,
|
||||
String name,
|
||||
String customerNumber,
|
||||
String companyName,
|
||||
String contactPerson,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String email,
|
||||
String phoneNumber,
|
||||
boolean active,
|
||||
LocalDateTime updatedAt
|
||||
) {
|
||||
}
|
||||
|
||||
public record FarmerMutation(String id, String name, String email, boolean active) {
|
||||
public record FarmerMutation(
|
||||
String id,
|
||||
String customerNumber,
|
||||
String companyName,
|
||||
String contactPerson,
|
||||
String street,
|
||||
String houseNumber,
|
||||
String postalCode,
|
||||
String city,
|
||||
String email,
|
||||
String phoneNumber,
|
||||
boolean active
|
||||
) {
|
||||
}
|
||||
|
||||
public record MedicationRow(
|
||||
|
||||
@@ -14,10 +14,8 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
|
||||
@@ -31,7 +31,7 @@ public class PortalService {
|
||||
LocalDate date
|
||||
) {
|
||||
List<CatalogService.FarmerOption> matchingFarmers = catalogService.activeCatalogSummary(actorId).farmers().stream()
|
||||
.filter(farmer -> farmerQuery == null || farmerQuery.isBlank() || farmer.name().toLowerCase(Locale.ROOT).contains(farmerQuery.toLowerCase(Locale.ROOT)))
|
||||
.filter(farmer -> farmerQuery == null || farmerQuery.isBlank() || farmer.companyName().toLowerCase(Locale.ROOT).contains(farmerQuery.toLowerCase(Locale.ROOT)))
|
||||
.toList();
|
||||
|
||||
List<PortalSampleRow> sampleRows;
|
||||
|
||||
@@ -124,7 +124,7 @@ public class SampleService {
|
||||
null,
|
||||
sampleNumber,
|
||||
farmer.businessKey(),
|
||||
farmer.name(),
|
||||
farmer.companyName(),
|
||||
farmer.email(),
|
||||
request.cowNumber().trim(),
|
||||
blankToNull(request.cowName()),
|
||||
@@ -178,7 +178,7 @@ public class SampleService {
|
||||
existing.id(),
|
||||
existing.sampleNumber(),
|
||||
farmer.businessKey(),
|
||||
farmer.name(),
|
||||
farmer.companyName(),
|
||||
farmer.email(),
|
||||
request.cowNumber().trim(),
|
||||
blankToNull(request.cowName()),
|
||||
|
||||
@@ -27,7 +27,8 @@ export type UserRole = "ADMIN" | "CUSTOMER";
|
||||
|
||||
export interface FarmerOption {
|
||||
businessKey: string;
|
||||
name: string;
|
||||
companyName: string;
|
||||
contactPerson: string | null;
|
||||
email: string | null;
|
||||
}
|
||||
|
||||
@@ -204,8 +205,15 @@ export interface SampleDetail {
|
||||
export interface FarmerRow {
|
||||
id: string;
|
||||
businessKey: string;
|
||||
name: string;
|
||||
customerNumber: string | null;
|
||||
companyName: string;
|
||||
contactPerson: string | null;
|
||||
street: string | null;
|
||||
houseNumber: string | null;
|
||||
postalCode: string | null;
|
||||
city: string | null;
|
||||
email: string | null;
|
||||
phoneNumber: string | null;
|
||||
active: boolean;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,23 @@ type DatasetKey = "farmers" | "medications" | "pathogens" | "antibiotics";
|
||||
type EditableRow = {
|
||||
id: string;
|
||||
businessKey: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
updatedAt: string;
|
||||
// Farmer fields
|
||||
customerNumber?: string;
|
||||
companyName?: string;
|
||||
contactPerson?: string;
|
||||
street?: string;
|
||||
houseNumber?: string;
|
||||
postalCode?: string;
|
||||
city?: string;
|
||||
email?: string;
|
||||
phoneNumber?: string;
|
||||
// Other fields
|
||||
name?: string;
|
||||
category?: MedicationCategory;
|
||||
code?: string;
|
||||
kind?: PathogenKind;
|
||||
active: boolean;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type DatasetsState = Record<DatasetKey, EditableRow[]>;
|
||||
@@ -33,13 +43,51 @@ const DATASET_TITLES: Record<DatasetKey, string> = {
|
||||
antibiotics: "Die Verwaltung der Antibiogramme",
|
||||
};
|
||||
|
||||
const FARMER_REQUIRED_FIELDS: Array<keyof EditableRow> = [
|
||||
"companyName",
|
||||
"customerNumber",
|
||||
"contactPerson",
|
||||
"street",
|
||||
"houseNumber",
|
||||
"postalCode",
|
||||
"city",
|
||||
"email",
|
||||
"phoneNumber",
|
||||
];
|
||||
|
||||
function isBlankValue(value: string | undefined) {
|
||||
return !value?.trim();
|
||||
}
|
||||
|
||||
function isFarmerFieldInvalid(row: EditableRow, field: keyof EditableRow, showValidation: boolean) {
|
||||
if (!showValidation) {
|
||||
return false;
|
||||
}
|
||||
const value = row[field];
|
||||
return typeof value !== "string" || isBlankValue(value);
|
||||
}
|
||||
|
||||
function isFarmerRowIncomplete(row: EditableRow) {
|
||||
return FARMER_REQUIRED_FIELDS.some((field) => {
|
||||
const value = row[field];
|
||||
return typeof value !== "string" || isBlankValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOverview(overview: AdministrationOverview): DatasetsState {
|
||||
return {
|
||||
farmers: overview.farmers.map((entry) => ({
|
||||
id: entry.id,
|
||||
businessKey: entry.businessKey,
|
||||
name: entry.name,
|
||||
customerNumber: entry.customerNumber ?? "",
|
||||
companyName: entry.companyName,
|
||||
contactPerson: entry.contactPerson ?? "",
|
||||
street: entry.street ?? "",
|
||||
houseNumber: entry.houseNumber ?? "",
|
||||
postalCode: entry.postalCode ?? "",
|
||||
city: entry.city ?? "",
|
||||
email: entry.email ?? "",
|
||||
phoneNumber: entry.phoneNumber ?? "",
|
||||
active: entry.active,
|
||||
updatedAt: entry.updatedAt,
|
||||
})),
|
||||
@@ -74,7 +122,21 @@ function normalizeOverview(overview: AdministrationOverview): DatasetsState {
|
||||
function emptyRow(dataset: DatasetKey): EditableRow {
|
||||
switch (dataset) {
|
||||
case "farmers":
|
||||
return { id: "", businessKey: "", name: "", email: "", active: true, updatedAt: new Date().toISOString() };
|
||||
return {
|
||||
id: "",
|
||||
businessKey: "",
|
||||
customerNumber: "",
|
||||
companyName: "",
|
||||
contactPerson: "",
|
||||
street: "",
|
||||
houseNumber: "",
|
||||
postalCode: "",
|
||||
city: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
active: true,
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
case "medications":
|
||||
return {
|
||||
id: "",
|
||||
@@ -133,6 +195,7 @@ export default function AdministrationPage() {
|
||||
}, []);
|
||||
|
||||
const rows = useMemo(() => datasets?.[selectedDataset] ?? [], [datasets, selectedDataset]);
|
||||
const isFarmerDataset = selectedDataset === "farmers";
|
||||
|
||||
function updateRow(index: number, patch: Partial<EditableRow>) {
|
||||
setDatasets((current) => {
|
||||
@@ -166,7 +229,11 @@ export default function AdministrationPage() {
|
||||
return;
|
||||
}
|
||||
setShowValidation(true);
|
||||
if (rows.some((row) => !row.name.trim())) {
|
||||
if (selectedDataset === "farmers" && rows.some(isFarmerRowIncomplete)) {
|
||||
setMessage("Bitte alle Pflichtfelder fuer den Landwirt ausfuellen.");
|
||||
return;
|
||||
}
|
||||
if (selectedDataset !== "farmers" && rows.some((row) => !row.name?.trim())) {
|
||||
setMessage("Bitte alle Pflichtfelder ausfuellen.");
|
||||
return;
|
||||
}
|
||||
@@ -178,8 +245,15 @@ export default function AdministrationPage() {
|
||||
case "farmers":
|
||||
response = await apiPost<EditableRow[]>("/catalog/farmers", rows.map((row) => ({
|
||||
id: row.id || null,
|
||||
name: row.name,
|
||||
customerNumber: row.customerNumber || null,
|
||||
companyName: row.companyName,
|
||||
contactPerson: row.contactPerson || null,
|
||||
street: row.street || null,
|
||||
houseNumber: row.houseNumber || null,
|
||||
postalCode: row.postalCode || null,
|
||||
city: row.city || null,
|
||||
email: row.email || null,
|
||||
phoneNumber: row.phoneNumber || null,
|
||||
active: row.active,
|
||||
})));
|
||||
break;
|
||||
@@ -245,12 +319,130 @@ export default function AdministrationPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isFarmerDataset ? (
|
||||
<div className="admin-farmer-list">
|
||||
{rows.map((row, index) => (
|
||||
<article key={`${row.id || "new"}-${index}`} className="admin-farmer-card">
|
||||
<div className="admin-farmer-card__row admin-farmer-card__row--primary">
|
||||
<label className="field field--required">
|
||||
<span>Firmenname</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "companyName", showValidation) ? "is-invalid" : ""}
|
||||
value={row.companyName ?? ""}
|
||||
onChange={(event) => updateRow(index, { companyName: event.target.value })}
|
||||
placeholder="Firmenname"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Ansprechpartner</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "contactPerson", showValidation) ? "is-invalid" : ""}
|
||||
value={row.contactPerson ?? ""}
|
||||
onChange={(event) => updateRow(index, { contactPerson: event.target.value })}
|
||||
placeholder="Ansprechpartner"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Kunden-Nr.</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "customerNumber", showValidation) ? "is-invalid" : ""}
|
||||
value={row.customerNumber ?? ""}
|
||||
onChange={(event) => updateRow(index, { customerNumber: event.target.value })}
|
||||
placeholder="Kunden-Nr."
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="admin-farmer-card__row admin-farmer-card__row--address">
|
||||
<label className="field field--required">
|
||||
<span>PLZ</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "postalCode", showValidation) ? "is-invalid" : ""}
|
||||
value={row.postalCode ?? ""}
|
||||
onChange={(event) => updateRow(index, { postalCode: event.target.value })}
|
||||
placeholder="PLZ"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Ort</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "city", showValidation) ? "is-invalid" : ""}
|
||||
value={row.city ?? ""}
|
||||
onChange={(event) => updateRow(index, { city: event.target.value })}
|
||||
placeholder="Ort"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Straße</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "street", showValidation) ? "is-invalid" : ""}
|
||||
value={row.street ?? ""}
|
||||
onChange={(event) => updateRow(index, { street: event.target.value })}
|
||||
placeholder="Straße"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Hausnummer</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "houseNumber", showValidation) ? "is-invalid" : ""}
|
||||
value={row.houseNumber ?? ""}
|
||||
onChange={(event) => updateRow(index, { houseNumber: event.target.value })}
|
||||
placeholder="Hausnummer"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="admin-farmer-card__row admin-farmer-card__row--contact">
|
||||
<label className="field field--required">
|
||||
<span>E-Mail</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "email", showValidation) ? "is-invalid" : ""}
|
||||
value={row.email ?? ""}
|
||||
onChange={(event) => updateRow(index, { email: event.target.value })}
|
||||
placeholder="E-Mail"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label className="field field--required">
|
||||
<span>Telefon</span>
|
||||
<input
|
||||
className={isFarmerFieldInvalid(row, "phoneNumber", showValidation) ? "is-invalid" : ""}
|
||||
value={row.phoneNumber ?? ""}
|
||||
onChange={(event) => updateRow(index, { phoneNumber: event.target.value })}
|
||||
placeholder="Telefon"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="admin-farmer-card__row admin-farmer-card__row--toggle">
|
||||
<label className="field">
|
||||
<span>Aktiv</span>
|
||||
<button
|
||||
type="button"
|
||||
className={`eye-button admin-farmer-card__toggle ${row.active ? "is-active" : "is-inactive"}`}
|
||||
onClick={() => updateRow(index, { active: !row.active })}
|
||||
>
|
||||
{row.active ? "sichtbar" : "inaktiv"}
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="table-shell">
|
||||
<table className="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="required-label">Name</th>
|
||||
{selectedDataset === "farmers" ? <th>E-Mail</th> : null}
|
||||
{selectedDataset === "medications" ? <th>Kategorie</th> : null}
|
||||
{selectedDataset === "pathogens" || selectedDataset === "antibiotics" ? <th>Kuerzel</th> : null}
|
||||
{selectedDataset === "pathogens" ? <th>Typ</th> : null}
|
||||
@@ -262,19 +454,11 @@ export default function AdministrationPage() {
|
||||
<tr key={`${row.id || "new"}-${index}`}>
|
||||
<td>
|
||||
<input
|
||||
className={showValidation && !row.name.trim() ? "is-invalid" : ""}
|
||||
value={row.name}
|
||||
className={showValidation && !row.name?.trim() ? "is-invalid" : ""}
|
||||
value={row.name ?? ""}
|
||||
onChange={(event) => updateRow(index, { name: event.target.value })}
|
||||
/>
|
||||
</td>
|
||||
{selectedDataset === "farmers" ? (
|
||||
<td>
|
||||
<input
|
||||
value={row.email ?? ""}
|
||||
onChange={(event) => updateRow(index, { email: event.target.value })}
|
||||
/>
|
||||
</td>
|
||||
) : null}
|
||||
{selectedDataset === "medications" ? (
|
||||
<td>
|
||||
<select
|
||||
@@ -326,6 +510,7 @@ export default function AdministrationPage() {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="page-actions page-actions--space-between">
|
||||
<button type="button" className="secondary-button" onClick={addRow}>
|
||||
|
||||
@@ -196,7 +196,7 @@ export default function SampleRegistrationPage() {
|
||||
>
|
||||
{catalogs?.farmers.map((farmer) => (
|
||||
<option key={farmer.businessKey} value={farmer.businessKey}>
|
||||
{farmer.name}
|
||||
{farmer.companyName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function SearchFarmerPage() {
|
||||
`/portal/snapshot?farmerBusinessKey=${encodeURIComponent(farmer.businessKey)}`,
|
||||
);
|
||||
setSamples(response.samples);
|
||||
setResultLabel(`Proben von ${farmer.name}`);
|
||||
setResultLabel(`Proben von ${farmer.companyName}`);
|
||||
setMessage(response.samples.length ? null : "Zu diesem Landwirt wurden noch keine Proben gefunden.");
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function SearchFarmerPage() {
|
||||
className="user-card"
|
||||
onClick={() => void loadFarmerSamples(farmer)}
|
||||
>
|
||||
<strong>{farmer.name}</strong>
|
||||
<strong>{farmer.companyName}</strong>
|
||||
<small>{farmer.email ?? "ohne E-Mail"}</small>
|
||||
</button>
|
||||
))}
|
||||
|
||||
@@ -570,6 +570,46 @@ a {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.admin-farmer-list {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.admin-farmer-card {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
padding: 24px;
|
||||
border: 1px solid rgba(37, 49, 58, 0.08);
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.42);
|
||||
}
|
||||
|
||||
.admin-farmer-card__row {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--primary {
|
||||
grid-template-columns: minmax(0, 1.8fr) minmax(0, 1.3fr) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--address {
|
||||
grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.05fr) minmax(0, 1.8fr) minmax(0, 0.9fr);
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--contact {
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.1fr);
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--toggle {
|
||||
grid-template-columns: minmax(0, 180px);
|
||||
}
|
||||
|
||||
.admin-farmer-card__toggle {
|
||||
min-width: 140px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -590,6 +630,35 @@ a {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.admin-farmer-card__row--primary,
|
||||
.admin-farmer-card__row--address,
|
||||
.admin-farmer-card__row--contact {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--toggle {
|
||||
grid-template-columns: minmax(0, 180px);
|
||||
}
|
||||
|
||||
.admin-farmer-card__toggle {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.admin-farmer-card {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.admin-farmer-card__row--primary,
|
||||
.admin-farmer-card__row--address,
|
||||
.admin-farmer-card__row--contact,
|
||||
.admin-farmer-card__row--toggle {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.status-pill,
|
||||
.info-chip {
|
||||
display: inline-flex;
|
||||
|
||||
Reference in New Issue
Block a user