Initial commit
This commit is contained in:
325
resources/js/components/RoundFileUpload/HeaderFormFields.tsx
Normal file
325
resources/js/components/RoundFileUpload/HeaderFormFields.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
import React from "react";
|
||||
import { Button, Input, Select, SelectItem } from "@heroui/react";
|
||||
import { type TFunction } from "i18next";
|
||||
import { type EdiHeaderForm, type PSectResult } from "@/types/edi";
|
||||
import { type BandOption } from "@/hooks/useRoundMeta";
|
||||
|
||||
type HeaderFormFieldsProps = {
|
||||
headerForm: EdiHeaderForm;
|
||||
setHeaderForm: React.Dispatch<React.SetStateAction<EdiHeaderForm>>;
|
||||
markEdited: () => void;
|
||||
onPbandChange: (value: string) => void;
|
||||
onSpoweChange: (value: string) => void;
|
||||
onApplyPsectCanonical: () => void;
|
||||
t: TFunction<"common">;
|
||||
tNameInvalid: boolean;
|
||||
tDateError: string | null;
|
||||
pCallEmpty: boolean;
|
||||
pCallInvalid: boolean;
|
||||
pwwloEmpty: boolean;
|
||||
pwwloFormatInvalid: boolean;
|
||||
pwwloInvalid: boolean;
|
||||
psectMissing: boolean;
|
||||
psectHasErrors: boolean;
|
||||
psectValidation: PSectResult;
|
||||
psectNeedsFormat: boolean;
|
||||
psectCanonical: string | null;
|
||||
shouldShowIaruAdjustButton: boolean;
|
||||
bands: BandOption[];
|
||||
bandsLoading: boolean;
|
||||
bandsError: string | null;
|
||||
bandUnknown: boolean;
|
||||
bandMissing: boolean;
|
||||
bandValue: string;
|
||||
pbandInfo: string | null;
|
||||
rhbbsWarning: string | null;
|
||||
spoweInvalid: boolean;
|
||||
spoweEmpty: boolean;
|
||||
spoweTooLong: boolean;
|
||||
spoweOverLimit: boolean;
|
||||
spoweLimitError: string | null;
|
||||
spoweInfo: string | null;
|
||||
santeInvalid: boolean;
|
||||
santeValue: string;
|
||||
santeTooLong: boolean;
|
||||
sectionNeedsSingle: boolean;
|
||||
sectionNeedsMulti: boolean;
|
||||
rcallInvalid: boolean;
|
||||
mopeInvalid: boolean;
|
||||
};
|
||||
|
||||
export default function HeaderFormFields({
|
||||
headerForm,
|
||||
setHeaderForm,
|
||||
markEdited,
|
||||
onPbandChange,
|
||||
onSpoweChange,
|
||||
onApplyPsectCanonical,
|
||||
t,
|
||||
tNameInvalid,
|
||||
tDateError,
|
||||
pCallEmpty,
|
||||
pCallInvalid,
|
||||
pwwloEmpty,
|
||||
pwwloFormatInvalid,
|
||||
pwwloInvalid,
|
||||
psectMissing,
|
||||
psectHasErrors,
|
||||
psectValidation,
|
||||
psectNeedsFormat,
|
||||
psectCanonical,
|
||||
shouldShowIaruAdjustButton,
|
||||
bands,
|
||||
bandsLoading,
|
||||
bandsError,
|
||||
bandUnknown,
|
||||
bandMissing,
|
||||
bandValue,
|
||||
pbandInfo,
|
||||
rhbbsWarning,
|
||||
spoweInvalid,
|
||||
spoweEmpty,
|
||||
spoweTooLong,
|
||||
spoweOverLimit,
|
||||
spoweLimitError,
|
||||
spoweInfo,
|
||||
santeInvalid,
|
||||
santeValue,
|
||||
santeTooLong,
|
||||
sectionNeedsSingle,
|
||||
sectionNeedsMulti,
|
||||
rcallInvalid,
|
||||
mopeInvalid,
|
||||
}: HeaderFormFieldsProps) {
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
label="TName"
|
||||
value={headerForm.TName}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, TName: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={tNameInvalid}
|
||||
errorMessage={tNameInvalid ? "TName je povinné." : undefined}
|
||||
/>
|
||||
<Input
|
||||
label="TDate (YYYYMMDD;YYYYMMDD)"
|
||||
value={headerForm.TDate}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, TDate: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={!!tDateError}
|
||||
errorMessage={tDateError || undefined}
|
||||
/>
|
||||
<Input
|
||||
label="PCall"
|
||||
value={headerForm.PCall}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, PCall: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={pCallInvalid}
|
||||
errorMessage={
|
||||
pCallEmpty ? "PCall je povinné." : pCallInvalid ? "PCall musí být validní volací znak." : undefined
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label="PWWLo"
|
||||
value={headerForm.PWWLo}
|
||||
onChange={(e) => {
|
||||
const normalized = e.target.value.toUpperCase();
|
||||
setHeaderForm((prev) => ({ ...prev, PWWLo: normalized }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={pwwloInvalid}
|
||||
errorMessage={
|
||||
pwwloEmpty
|
||||
? "PWWLo je povinné."
|
||||
: pwwloFormatInvalid
|
||||
? "PWWLo musí mít 6 znaků ve formátu lokátoru (AA00AA)."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-end gap-2">
|
||||
<Input
|
||||
className="flex-1"
|
||||
label="PSect"
|
||||
value={headerForm.PSect}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, PSect: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={psectMissing || psectHasErrors}
|
||||
errorMessage={
|
||||
psectMissing
|
||||
? (t("upload_error_psect_required") as string) || "PSect je povinné."
|
||||
: psectHasErrors
|
||||
? psectValidation.errors.join(" ")
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{shouldShowIaruAdjustButton && (
|
||||
<Button
|
||||
size="lg"
|
||||
variant="flat"
|
||||
className="shrink-0"
|
||||
onPress={onApplyPsectCanonical}
|
||||
isDisabled={psectMissing}
|
||||
>
|
||||
{psectCanonical ? (
|
||||
<span className="flex flex-col text-left leading-tight">
|
||||
<span>{(t("upload_psect_format_button") as string) || "Upravit kategorie podle IARU"}</span>
|
||||
<span className="text-xs text-foreground-500">{psectCanonical}</span>
|
||||
</span>
|
||||
) : (
|
||||
(t("upload_psect_format_button") as string) || "Upravit kategorie podle IARU"
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{psectNeedsFormat && (
|
||||
<div className="text-xs text-foreground-500">
|
||||
{(t("upload_error_psect_not_iaru") as string) || "PSect není ve formátu IARU."}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{bands.length > 0 ? (
|
||||
<Select
|
||||
label="PBand"
|
||||
selectedKeys={!bandUnknown && headerForm.PBand ? [headerForm.PBand] : []}
|
||||
onChange={(e) => onPbandChange(e.target.value)}
|
||||
isLoading={bandsLoading}
|
||||
isInvalid={bandUnknown || bandMissing}
|
||||
errorMessage={
|
||||
bandUnknown
|
||||
? `Neznámé pásmo "${bandValue}", vyber správnou hodnotu.`
|
||||
: bandMissing
|
||||
? "PBand není vyplněné, vyber pásmo ze seznamu."
|
||||
: bandsError ?? undefined
|
||||
}
|
||||
>
|
||||
{bands.map((band) => (
|
||||
<SelectItem key={band.name} value={band.name}>
|
||||
{band.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
) : (
|
||||
<Input
|
||||
label="PBand"
|
||||
value={headerForm.PBand}
|
||||
onChange={(e) => onPbandChange(e.target.value)}
|
||||
isDisabled={bandsLoading}
|
||||
errorMessage={bandsError ?? undefined}
|
||||
/>
|
||||
)}
|
||||
{pbandInfo && <div className="text-xs text-foreground-500">{pbandInfo}</div>}
|
||||
</div>
|
||||
<Input
|
||||
label="RHBBS (e-mail)"
|
||||
value={headerForm.RHBBS}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, RHBBS: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={false}
|
||||
/>
|
||||
{rhbbsWarning && <div className="text-xs text-amber-600">{rhbbsWarning}</div>}
|
||||
<div className="flex flex-col gap-1">
|
||||
<Input
|
||||
label="SPowe"
|
||||
value={headerForm.SPowe}
|
||||
onChange={(e) => onSpoweChange(e.target.value)}
|
||||
isInvalid={spoweInvalid}
|
||||
errorMessage={
|
||||
spoweEmpty
|
||||
? (t("upload_error_spowe_required") as string) || "SPowe je povinné."
|
||||
: spoweTooLong
|
||||
? (t("upload_error_spowe_length") as string) || "SPowe může mít maximálně 12 znaků."
|
||||
: spoweInvalid || spoweOverLimit
|
||||
? (t("upload_error_spowe_format") as string) || "SPowe musí být celé číslo (bez jednotek)."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{spoweLimitError && <div className="text-xs text-red-600">{spoweLimitError}</div>}
|
||||
{spoweInfo && <div className="text-xs text-foreground-500">{spoweInfo}</div>}
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
label="SAnte"
|
||||
value={headerForm.SAnte}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, SAnte: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={santeInvalid}
|
||||
errorMessage={
|
||||
santeValue === "" ? (t("upload_error_sante_required") as string) || "SAnte je povinné." : undefined
|
||||
}
|
||||
/>
|
||||
{santeTooLong && (
|
||||
<div className="text-xs text-warning-600">
|
||||
{t("upload_warn_sante_length") ??
|
||||
"Ve výsledcích bude zobrazeno pouze 12 znaků, váš popis antény je delší a bude oříznut."}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{(sectionNeedsSingle || sectionNeedsMulti) && (
|
||||
<Input
|
||||
label="RCall"
|
||||
value={headerForm.RCall}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, RCall: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={rcallInvalid}
|
||||
errorMessage={
|
||||
headerForm.RCall.trim() === ""
|
||||
? "RCall je povinné pro zvolenou kategorii."
|
||||
: rcallInvalid
|
||||
? "RCall musí být validní volací znak."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{sectionNeedsMulti && (
|
||||
<>
|
||||
<Input
|
||||
label="MOpe1"
|
||||
value={headerForm.MOpe1}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, MOpe1: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={mopeInvalid}
|
||||
errorMessage={
|
||||
mopeInvalid
|
||||
? (t("upload_error_mope_missing") as string) ||
|
||||
"Vyplň alespoň MOpe1 nebo MOpe2 (můžeš zadat více značek oddělených mezerou/středníkem/čárkou)."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
label="MOpe2"
|
||||
value={headerForm.MOpe2}
|
||||
onChange={(e) => {
|
||||
setHeaderForm((prev) => ({ ...prev, MOpe2: e.target.value }));
|
||||
markEdited();
|
||||
}}
|
||||
isInvalid={mopeInvalid}
|
||||
errorMessage={
|
||||
mopeInvalid
|
||||
? (t("upload_error_mope_missing") as string) ||
|
||||
"Vyplň alespoň MOpe1 nebo MOpe2 (můžeš zadat více značek oddělených mezerou/středníkem/čárkou)."
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
32
resources/js/components/RoundFileUpload/UploadMessages.tsx
Normal file
32
resources/js/components/RoundFileUpload/UploadMessages.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
type UploadMessagesProps = {
|
||||
isEdiFile: boolean;
|
||||
unsupportedInfo: string | null;
|
||||
qsoCountWarning: string | null;
|
||||
qsoCallsignInfo: string | null;
|
||||
rhbbsWarning: string | null;
|
||||
error: string | null;
|
||||
success: string | null;
|
||||
};
|
||||
|
||||
export default function UploadMessages({
|
||||
isEdiFile,
|
||||
unsupportedInfo,
|
||||
qsoCountWarning,
|
||||
qsoCallsignInfo,
|
||||
rhbbsWarning,
|
||||
error,
|
||||
success,
|
||||
}: UploadMessagesProps) {
|
||||
return (
|
||||
<>
|
||||
{!isEdiFile && unsupportedInfo && <div className="text-sm text-red-600">{unsupportedInfo}</div>}
|
||||
{qsoCountWarning && <div className="text-sm text-amber-600">{qsoCountWarning}</div>}
|
||||
{qsoCallsignInfo && <div className="text-sm text-amber-600 whitespace-pre-line">{qsoCallsignInfo}</div>}
|
||||
{rhbbsWarning && <div className="text-sm text-amber-600 whitespace-pre-line">{rhbbsWarning}</div>}
|
||||
{error && <div className="text-sm text-red-600 whitespace-pre-line">{error}</div>}
|
||||
{success && <div className="text-sm text-green-600">{success}</div>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user