diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java index 7808bf0..5d0b48f 100644 --- a/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/FormsFragment.java @@ -1,18 +1,26 @@ package com.kbs.kbsintranett; +import android.app.DatePickerDialog; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; +import android.text.Editable; import android.text.Html; import android.text.InputType; +import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -28,8 +36,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,8 +55,10 @@ public class FormsFragment extends Fragment { private static final String TAG = "FormsFragment"; private static final String BASE_URL_GF = "https://intranet.kbs.no/wp-json/gf/v2"; - // ID 1 = Ansatteopplysninger (Skal ha autofyll fra historikk) + // SKJEMA ID-er private static final int ID_ANSATTEOPPLYSNINGER = 1; + private static final int ID_HMS_BEKREFTELSE = 10; + private static final int ID_EGENMELDING = 11; private int formId = 1; @@ -58,25 +68,27 @@ public class FormsFragment extends Fragment { private TextView lblHistory; // Overskriften "Tidligere innsendinger" private ProgressBar loadingSpinner; - private Map dynamicFields = new HashMap<>(); + // Vi lagrer View-objektene for å kunne toggle synlighet (Conditional Logic) + // Key = Field ID (f.eks "2", "7.1") + private Map fieldWrappers = new HashMap<>(); // Beholderen (Label + Input) + private Map inputViews = new HashMap<>(); // Selve input-feltet (EditText, RadioGroup, etc) private Map requiredFieldsMap = new HashMap<>(); + private GravityForm currentForm; // Holder referanse til skjemaet for logikk-sjekk + private final OkHttpClient client = new OkHttpClient(); @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_forms, container, false); - formContainer = view.findViewById(R.id.form_container); historyContainer = view.findViewById(R.id.historyContainer); txtStatus = view.findViewById(R.id.txt_status); lblHistory = view.findViewById(R.id.lbl_history); loadingSpinner = view.findViewById(R.id.loading_spinner); - // Fallback hvis XML feiler if (formContainer == null) { - // Hvis brukeren ikke har oppdatert XML ennå, unngå krasj formContainer = new LinearLayout(getContext()); } @@ -99,11 +111,11 @@ public class FormsFragment extends Fragment { @Override public void onResponse(retrofit2.Call call, retrofit2.Response response) { if (response.isSuccessful() && response.body() != null) { - GravityForm form = response.body(); + currentForm = response.body(); if (getActivity() != null) { getActivity().runOnUiThread(() -> { if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); - renderDynamicForm(form); + renderDynamicForm(currentForm); fetchFormEntries(); }); } @@ -124,7 +136,8 @@ public class FormsFragment extends Fragment { private void renderDynamicForm(GravityForm form) { if (formContainer == null) return; formContainer.removeAllViews(); - dynamicFields.clear(); + fieldWrappers.clear(); + inputViews.clear(); requiredFieldsMap.clear(); updateStatus(""); @@ -146,96 +159,85 @@ public class FormsFragment extends Fragment { if (form.fields == null) return; - UserManager user = UserManager.getInstance(); - boolean isPersonaliaSection = true; - - boolean hasFilledName = false; - boolean hasFilledEmail = false; - boolean hasFilledPhone = false; - boolean hasFilledJob = false; - + // Bygg feltene for (GravityField field : form.fields) { - - if ("hidden".equals(field.type)) continue; - if (field.isHidden) continue; - if ("hidden".equals(field.visibility)) continue; - - if ("section".equals(field.type)) { - String labelLower = field.label.toLowerCase(); - isPersonaliaSection = labelLower.contains("personalia"); - addSectionHeader(field.label, field.description); + // Ignorerte felttyper + if ("hidden".equals(field.type) || field.isHidden || "hidden".equals(field.visibility)) { continue; } + // Wrapper for hele feltet (Label + Input + Description) + LinearLayout fieldWrapper = new LinearLayout(getContext()); + fieldWrapper.setOrientation(LinearLayout.VERTICAL); + fieldWrapper.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + fieldWrapper.setPadding(0, 10, 0, 20); + + // Lagre referanse til wrapperen basert på Felt-ID + fieldWrappers.put(String.valueOf(field.id), fieldWrapper); + + // 1. Seksjoner (Overskrifter) + if ("section".equals(field.type)) { + addSectionHeader(fieldWrapper, field.label, field.description); + formContainer.addView(fieldWrapper); + continue; + } + + // 2. HTML-blokker if ("html".equals(field.type)) { - if (field.content != null && field.content.toLowerCase().contains("pårørende")) { - isPersonaliaSection = false; - } if (field.content != null && !field.content.isEmpty()) { TextView htmlView = new TextView(getContext()); htmlView.setText(Html.fromHtml(field.content, Html.FROM_HTML_MODE_COMPACT)); - htmlView.setPadding(0, 40, 0, 10); - formContainer.addView(htmlView); + fieldWrapper.addView(htmlView); } + formContainer.addView(fieldWrapper); continue; } - if (field.inputs != null && !field.inputs.isEmpty()) { - renderCompositeField(field, isPersonaliaSection); - continue; - } - - // ENKELTFELT + // 3. Label (Overskrift for feltet) TextView label = new TextView(getContext()); String labelText = field.label; if (field.isRequired) labelText += " *"; label.setText(labelText); label.setTextColor(Color.DKGRAY); - label.setPadding(0, 25, 0, 5); - formContainer.addView(label); + label.setTypeface(null, Typeface.BOLD); + label.setPadding(0, 10, 0, 5); + fieldWrapper.addView(label); - EditText input = new EditText(getContext()); - input.setPadding(30, 30, 30, 30); - input.setBackgroundResource(android.R.drawable.edit_text); - - // Autofyll fra UserManager (Standardinfo som alltid er greit å fylle ut) - if (isPersonaliaSection) { - String lowerLabel = field.label.toLowerCase(); - - if (!hasFilledName && (lowerLabel.equals("navn") || lowerLabel.equals("navn *"))) { - input.setText(user.getUserDisplayName()); - hasFilledName = true; + // 4. Input-felt basert på type + if (field.inputs != null && !field.inputs.isEmpty()) { + // Sammensatte felt (Navn, Adresse, Checkbox med inputs) + if ("consent".equals(field.type)) { + renderConsentField(fieldWrapper, field); + } else if ("checkbox".equals(field.type)) { + renderCheckboxField(fieldWrapper, field); + } else { + renderCompositeField(fieldWrapper, field); } - else if (!hasFilledEmail && lowerLabel.contains("e-post") && lowerLabel.contains("arbeid")) { - input.setText(user.getUserEmail()); - hasFilledEmail = true; - } - else if (!hasFilledJob && lowerLabel.contains("stilling")) { - input.setText(user.getStilling()); - hasFilledJob = true; - } - else if (!hasFilledPhone && (lowerLabel.contains("mobil") || lowerLabel.equals("mobiltelefon"))) { - input.setText(user.getMobiltelefon()); - hasFilledPhone = true; - } - } - - if ("number".equals(field.type) || "phone".equals(field.type)) { - input.setInputType(InputType.TYPE_CLASS_PHONE); - } else if ("email".equals(field.type)) { - input.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + } else if ("radio".equals(field.type)) { + renderRadioField(fieldWrapper, field); + } else if ("select".equals(field.type)) { + renderSelectField(fieldWrapper, field); + } else if ("textarea".equals(field.type)) { + renderTextAreaField(fieldWrapper, field); + } else if ("date".equals(field.type)) { + renderDateField(fieldWrapper, field); + } else if ("consent".equals(field.type)) { + renderConsentField(fieldWrapper, field); } else { - input.setInputType(InputType.TYPE_CLASS_TEXT); + // Standard tekst, e-post, telefon, nummer + renderTextField(fieldWrapper, field); } - formContainer.addView(input); - + // 5. Beskrivelse (hvis finnes) if (field.description != null && !field.description.isEmpty()) { - addDescription(field.description); + TextView desc = new TextView(getContext()); + desc.setText(Html.fromHtml(field.description, Html.FROM_HTML_MODE_COMPACT)); + desc.setTextSize(12); + desc.setTextColor(Color.GRAY); + fieldWrapper.addView(desc); } - dynamicFields.put(String.valueOf(field.id), input); - requiredFieldsMap.put(String.valueOf(field.id), field.isRequired); + formContainer.addView(fieldWrapper); } // SEND-KNAPP @@ -243,54 +245,236 @@ public class FormsFragment extends Fragment { dynamicSubmit.setText("Send inn skjema"); dynamicSubmit.setTextColor(Color.WHITE); dynamicSubmit.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); params.setMargins(0, 60, 0, 20); dynamicSubmit.setLayoutParams(params); - dynamicSubmit.setOnClickListener(v -> submitDynamicForm()); formContainer.addView(dynamicSubmit); + + // Etter at alt er tegnet, kjør en initial sjekk på logikken + evaluateAllConditionalLogic(); } - private void renderCompositeField(GravityField parentField, boolean isPersonaliaSection) { - TextView groupLabel = new TextView(getContext()); - String groupText = parentField.label; - if (parentField.isRequired) groupText += " *"; - groupLabel.setText(groupText); - groupLabel.setTextColor(Color.DKGRAY); - groupLabel.setTypeface(null, Typeface.BOLD); - groupLabel.setPadding(0, 30, 0, 5); - formContainer.addView(groupLabel); + // --- RENDER METHODS --- + + private void renderTextField(LinearLayout container, GravityField field) { + EditText input = new EditText(getContext()); + input.setPadding(30, 30, 30, 30); + input.setBackgroundResource(android.R.drawable.edit_text); + + if ("number".equals(field.type) || "phone".equals(field.type)) { + input.setInputType(InputType.TYPE_CLASS_PHONE); + } else if ("email".equals(field.type)) { + input.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + } else { + input.setInputType(InputType.TYPE_CLASS_TEXT); + } UserManager user = UserManager.getInstance(); - String lowerParentLabel = parentField.label.toLowerCase(); + String lowerLabel = field.label.toLowerCase(); + + // Autofyll Logikk + if (formId == ID_ANSATTEOPPLYSNINGER) { + if (lowerLabel.contains("e-post") && lowerLabel.contains("arbeid")) { + input.setText(user.getUserEmail()); + } else if (lowerLabel.contains("stilling")) { + input.setText(user.getStilling()); + } else if ((lowerLabel.contains("mobil") || lowerLabel.equals("mobiltelefon"))) { + input.setText(user.getMobiltelefon()); + } + } + else if (formId == ID_HMS_BEKREFTELSE) { + if (lowerLabel.contains("e-post")) { + input.setText(user.getUserEmail()); + } + } + else if (formId == ID_EGENMELDING) { + if (lowerLabel.contains("navn")) { + input.setText(user.getUserDisplayName()); + } else if (lowerLabel.contains("stilling")) { + input.setText(user.getStilling()); + } else if (lowerLabel.contains("e-post")) { + input.setText(user.getUserEmail()); + } + } + + // Lytter for å trigge Conditional Logic når tekst endres + input.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + @Override + public void afterTextChanged(Editable s) { + evaluateAllConditionalLogic(); + } + }); + + container.addView(input); + inputViews.put(field.id, input); + requiredFieldsMap.put(field.id, field.isRequired); + } + + private void renderTextAreaField(LinearLayout container, GravityField field) { + EditText input = new EditText(getContext()); + input.setPadding(30, 30, 30, 30); + input.setBackgroundResource(android.R.drawable.edit_text); + input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + input.setMinLines(3); + input.setGravity(android.view.Gravity.TOP | android.view.Gravity.START); + + input.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + @Override + public void afterTextChanged(Editable s) { + evaluateAllConditionalLogic(); + } + }); + + container.addView(input); + inputViews.put(field.id, input); + requiredFieldsMap.put(field.id, field.isRequired); + } + + private void renderRadioField(LinearLayout container, GravityField field) { + RadioGroup group = new RadioGroup(getContext()); + group.setOrientation(RadioGroup.VERTICAL); + + if (field.choices != null) { + for (GravityField.Choice choice : field.choices) { + RadioButton rb = new RadioButton(getContext()); + rb.setText(choice.text); + rb.setTag(choice.value); + group.addView(rb); + } + } + + // Lytter for Conditional Logic + group.setOnCheckedChangeListener((group1, checkedId) -> evaluateAllConditionalLogic()); + + container.addView(group); + inputViews.put(field.id, group); + requiredFieldsMap.put(field.id, field.isRequired); + } + + private void renderSelectField(LinearLayout container, GravityField field) { + Spinner spinner = new Spinner(getContext()); + List labels = new ArrayList<>(); + labels.add("- Velg -"); + + if (field.choices != null) { + for (GravityField.Choice c : field.choices) { + labels.add(c.text); + } + } + + ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item, labels); + spinner.setAdapter(adapter); + + container.addView(spinner); + inputViews.put(field.id, spinner); + requiredFieldsMap.put(field.id, field.isRequired); + } + + private void renderConsentField(LinearLayout container, GravityField field) { + CheckBox checkBox = new CheckBox(getContext()); + String cbText = (field.checkboxLabel != null && !field.checkboxLabel.isEmpty()) + ? field.checkboxLabel + : field.label; + checkBox.setText(cbText); + checkBox.setTextSize(16); + checkBox.setPadding(10, 10, 10, 10); + + String inputId = field.id; + if (field.inputs != null && !field.inputs.isEmpty()) { + inputId = field.inputs.get(0).id; + } + + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> evaluateAllConditionalLogic()); + + container.addView(checkBox); + inputViews.put(inputId, checkBox); + requiredFieldsMap.put(inputId, field.isRequired); + } + + private void renderCheckboxField(LinearLayout container, GravityField field) { + // I Gravity Forms er checkboxes en liste av inputs hvis det er flere valg. + // Hvis det bare er ett valg (som "Jeg bekrefter..."), er det ofte én input. + + // Sjekk om det er inputs + if (field.inputs != null) { + for (GravityField inputDef : field.inputs) { + CheckBox checkBox = new CheckBox(getContext()); + checkBox.setText(inputDef.label); + checkBox.setTextSize(16); + checkBox.setPadding(10, 10, 10, 10); + + // Verdien som sendes er vanligvis etiketten hvis den er huket av. + checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> evaluateAllConditionalLogic()); + + container.addView(checkBox); + inputViews.put(inputDef.id, checkBox); + // Checkboxer er sjeldent individuelt påkrevd i en gruppe, men selve feltet er. + // Vi markerer den spesifikke boksen som påkrevd hvis feltet er det og det bare er én. + if (field.inputs.size() == 1) { + requiredFieldsMap.put(inputDef.id, field.isRequired); + } else { + // Hvis det er flere, er validering mer komplekst (minst én må velges). + // Foreløpig enkel implementasjon. + requiredFieldsMap.put(inputDef.id, false); + } + } + } + } + + private void renderDateField(LinearLayout container, GravityField field) { + EditText dateInput = new EditText(getContext()); + dateInput.setPadding(30, 30, 30, 30); + dateInput.setBackgroundResource(android.R.drawable.edit_text); + dateInput.setFocusable(false); // Hindre tastatur + dateInput.setClickable(true); // Tillat klikk + dateInput.setHint("dd.mm.yyyy"); + + dateInput.setOnClickListener(v -> { + final Calendar c = Calendar.getInstance(); + int year = c.get(Calendar.YEAR); + int month = c.get(Calendar.MONTH); + int day = c.get(Calendar.DAY_OF_MONTH); + + DatePickerDialog datePickerDialog = new DatePickerDialog(getContext(), + (view, year1, monthOfYear, dayOfMonth) -> { + // Format: dd.MM.yyyy + String selectedDate = String.format(java.util.Locale.getDefault(), "%02d.%02d.%d", dayOfMonth, monthOfYear + 1, year1); + dateInput.setText(selectedDate); + evaluateAllConditionalLogic(); + }, year, month, day); + datePickerDialog.show(); + }); + + container.addView(dateInput); + inputViews.put(field.id, dateInput); + requiredFieldsMap.put(field.id, field.isRequired); + } + + private void renderCompositeField(LinearLayout container, GravityField parentField) { + UserManager user = UserManager.getInstance(); + boolean isPersonalia = (formId == ID_ANSATTEOPPLYSNINGER); List inputs = new ArrayList<>(parentField.inputs); - - // Sortering for adresse - if (parentField.type.equals("address")) { - Collections.sort(inputs, new Comparator() { - @Override - public int compare(GravityField f1, GravityField f2) { - int s1 = getAddressScore(f1.label); - int s2 = getAddressScore(f2.label); - return Integer.compare(s1, s2); - } - }); + if ("address".equals(parentField.type)) { + Collections.sort(inputs, (f1, f2) -> Integer.compare(getAddressScore(f1.label), getAddressScore(f2.label))); } for (GravityField subField : inputs) { - if (subField.isHidden) continue; - if ("hidden".equals(subField.visibility)) continue; + if (subField.isHidden || "hidden".equals(subField.visibility)) continue; TextView subLabel = new TextView(getContext()); String subLabelText = subField.label; - boolean isSubRequired = parentField.isRequired; - if (parentField.type.equals("address") && subField.id.endsWith(".2")) { + if ("address".equals(parentField.type) && subField.id.endsWith(".2")) { isSubRequired = false; } if (isSubRequired) subLabelText += " *"; @@ -299,32 +483,47 @@ public class FormsFragment extends Fragment { subLabel.setTextColor(Color.GRAY); subLabel.setTextSize(12); subLabel.setPadding(0, 10, 0, 0); - formContainer.addView(subLabel); + container.addView(subLabel); EditText subInput = new EditText(getContext()); subInput.setPadding(30, 30, 30, 30); subInput.setBackgroundResource(android.R.drawable.edit_text); subInput.setInputType(InputType.TYPE_CLASS_TEXT); - if (isPersonaliaSection && lowerParentLabel.contains("navn") && !lowerParentLabel.contains("pårørende")) { - String lowerSubLabel = subField.label.toLowerCase(); - if (lowerSubLabel.contains("fornavn")) { - subInput.setText(user.getFirstName()); - } - else if (lowerSubLabel.contains("etternavn")) { - subInput.setText(user.getLastName()); - } + if (isPersonalia && parentField.label.toLowerCase().contains("navn") && !parentField.label.toLowerCase().contains("pårørende")) { + String lowerSub = subField.label.toLowerCase(); + if (lowerSub.contains("fornavn")) subInput.setText(user.getFirstName()); + else if (lowerSub.contains("etternavn")) subInput.setText(user.getLastName()); } - formContainer.addView(subInput); - - dynamicFields.put(subField.id, subInput); + container.addView(subInput); + inputViews.put(subField.id, subInput); requiredFieldsMap.put(subField.id, isSubRequired); } + } - if (parentField.description != null && !parentField.description.isEmpty()) { - addDescription(parentField.description); + private void addSectionHeader(LinearLayout container, String title, String descText) { + TextView sectionHeader = new TextView(getContext()); + sectionHeader.setText(title); + sectionHeader.setTextSize(18); + sectionHeader.setTypeface(null, Typeface.BOLD); + sectionHeader.setTextColor(Color.parseColor("#0069B3")); + sectionHeader.setPadding(0, 20, 0, 5); + container.addView(sectionHeader); + + if (descText != null && !descText.isEmpty()) { + TextView desc = new TextView(getContext()); + desc.setText(Html.fromHtml(descText, Html.FROM_HTML_MODE_COMPACT)); + desc.setTextSize(12); + desc.setTextColor(Color.GRAY); + container.addView(desc); } + + View line = new View(getContext()); + line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2)); + line.setBackgroundColor(Color.LTGRAY); + line.setPadding(0,0,0,20); + container.addView(line); } private int getAddressScore(String label) { @@ -338,34 +537,104 @@ public class FormsFragment extends Fragment { return 99; } - private void addDescription(String text) { - TextView desc = new TextView(getContext()); - desc.setText(Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT)); - desc.setTextSize(12); - desc.setTextColor(Color.GRAY); - desc.setPadding(5, 5, 5, 0); - formContainer.addView(desc); - } + // --- CONDITIONAL LOGIC ENGINE --- - private void addSectionHeader(String title, String descText) { - TextView sectionHeader = new TextView(getContext()); - sectionHeader.setText(title); - sectionHeader.setTextSize(18); - sectionHeader.setTypeface(null, Typeface.BOLD); - sectionHeader.setTextColor(Color.parseColor("#0069B3")); - sectionHeader.setPadding(0, 50, 0, 5); - formContainer.addView(sectionHeader); + private void evaluateAllConditionalLogic() { + if (currentForm == null || currentForm.fields == null) return; - if (descText != null && !descText.isEmpty()) { - addDescription(descText); + for (GravityField field : currentForm.fields) { + if (field.conditionalLogic == null) { + setViewVisibility(field.id, true); + continue; + } + + boolean isMatch = evaluateLogic(field.conditionalLogic); + boolean show = "show".equalsIgnoreCase(field.conditionalLogic.actionType); + boolean shouldBeVisible = (show && isMatch) || (!show && !isMatch); + setViewVisibility(field.id, shouldBeVisible); } - - View line = new View(getContext()); - line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 2)); - line.setBackgroundColor(Color.LTGRAY); - formContainer.addView(line); } + private boolean evaluateLogic(GravityField.ConditionalLogic logic) { + if (logic.rules == null || logic.rules.isEmpty()) return true; + + boolean isAll = "all".equalsIgnoreCase(logic.logicType); + boolean aggregatedResult = isAll; + + for (GravityField.Rule rule : logic.rules) { + String val = getInputValue(rule.fieldId); + boolean ruleMatch = checkRule(val, rule.operator, rule.value); + + if (isAll) { + aggregatedResult = aggregatedResult && ruleMatch; + if (!aggregatedResult) break; + } else { + aggregatedResult = aggregatedResult || ruleMatch; + if (aggregatedResult) break; + } + } + return aggregatedResult; + } + + private boolean checkRule(String actualValue, String operator, String targetValue) { + if (actualValue == null) actualValue = ""; + if (targetValue == null) targetValue = ""; + + switch (operator.toLowerCase()) { + case "is": + return actualValue.equalsIgnoreCase(targetValue); + case "isnot": + return !actualValue.equalsIgnoreCase(targetValue); + case "contains": + return actualValue.toLowerCase().contains(targetValue.toLowerCase()); + case "starts_with": + return actualValue.toLowerCase().startsWith(targetValue.toLowerCase()); + case "ends_with": + return actualValue.toLowerCase().endsWith(targetValue.toLowerCase()); + default: + return false; + } + } + + private String getInputValue(String fieldId) { + View view = inputViews.get(fieldId); + if (view == null) return ""; + + if (view instanceof EditText) { + return ((EditText) view).getText().toString(); + } else if (view instanceof RadioGroup) { + RadioGroup group = (RadioGroup) view; + int checkedId = group.getCheckedRadioButtonId(); + if (checkedId != -1) { + View rb = group.findViewById(checkedId); + if (rb != null && rb.getTag() != null) { + return rb.getTag().toString(); + } + } + } else if (view instanceof Spinner) { + Spinner spinner = (Spinner) view; + Object item = spinner.getSelectedItem(); + return item != null ? item.toString() : ""; + } else if (view instanceof CheckBox) { + CheckBox cb = (CheckBox) view; + // Hvis sjekket: returner teksten på boksen eller "1" (for consent) + // For standard checkbox i GF brukes ofte verdien som er valgt. + // Hvis det er en enkel avkryssing (type "Bekreftelse"), er "Ja" eller "1" vanlig. + // Vi returnerer teksten hvis den er sjekket, ellers tom streng. + return cb.isChecked() ? cb.getText().toString() : ""; + } + return ""; + } + + private void setViewVisibility(String fieldId, boolean visible) { + View wrapper = fieldWrappers.get(fieldId); + if (wrapper != null) { + wrapper.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + // --- DATA HANDLING --- + private void fetchFormEntries() { UserManager user = UserManager.getInstance(); String cookie = user.getCookie(); @@ -404,8 +673,8 @@ public class FormsFragment extends Fragment { getActivity().runOnUiThread(() -> { showHistory(entries); - // VIKTIG: AUTO-FYLL SKJER NÅ KUN FOR SKJEMA ID 1 - if (entries.length() > 0 && formId == ID_ANSATTEOPPLYSNINGER) { + // VIKTIG: AUTO-FYLL SKJER KUN FOR SKJEMA ID 1 ("Ansatteopplysninger") + if (formId == ID_ANSATTEOPPLYSNINGER && entries.length() > 0) { try { prefillFormFromHistory(entries.getJSONObject(0)); } catch (JSONException e) { e.printStackTrace(); } @@ -420,35 +689,74 @@ public class FormsFragment extends Fragment { } private void prefillFormFromHistory(JSONObject latestEntry) { - for (Map.Entry mapEntry : dynamicFields.entrySet()) { - String fieldId = mapEntry.getKey(); - EditText input = mapEntry.getValue(); + if (latestEntry == null) return; - if (input.getText().length() > 0) continue; + for (Map.Entry entry : inputViews.entrySet()) { + String fieldId = entry.getKey(); + View view = entry.getValue(); if (latestEntry.has(fieldId)) { - String prevValue = latestEntry.optString(fieldId); - if (!prevValue.isEmpty()) { - input.setText(prevValue); + String value = latestEntry.optString(fieldId); + if (value == null || value.isEmpty()) continue; + + if (view instanceof EditText) { + ((EditText) view).setText(value); + } else if (view instanceof RadioGroup) { + RadioGroup group = (RadioGroup) view; + for (int i = 0; i < group.getChildCount(); i++) { + View child = group.getChildAt(i); + if (child instanceof RadioButton) { + Object tag = child.getTag(); + if (tag != null && tag.toString().equalsIgnoreCase(value)) { + ((RadioButton) child).setChecked(true); + break; + } + } + } + } else if (view instanceof CheckBox) { + // For Consent er value "1" = Checked. + // For vanlig checkbox kan det være verdien av valget. + if ("1".equals(value) || "true".equalsIgnoreCase(value) || ((CheckBox)view).getText().toString().equals(value)) { + ((CheckBox) view).setChecked(true); + } } } } - updateStatus("Skjemaet er forhåndsutfylt fra din forrige innsending."); + updateStatus("Skjemaet er forhåndsutfylt fra din siste innsending."); + evaluateAllConditionalLogic(); } private void submitDynamicForm() { JSONObject inputValues = new JSONObject(); boolean hasValues = false; - for (Map.Entry entry : dynamicFields.entrySet()) { + for (Map.Entry entry : inputViews.entrySet()) { String fieldId = entry.getKey(); - EditText input = entry.getValue(); - String value = input.getText().toString().trim(); + View view = entry.getValue(); + // Hopp over hvis feltet er skjult av Conditional Logic + View wrapper = fieldWrappers.get(fieldId); + // Hvis wrapper er null (f.eks sub-input), sjekk parent wrapper + if (wrapper == null) { + // Forenklet sjekk: hvis selve viewet ikke vises på skjermen + if (!view.isShown()) continue; + } else { + if (wrapper.getVisibility() != View.VISIBLE) continue; + } + + String value = getInputValue(fieldId); + + // Validering Boolean isRequired = requiredFieldsMap.get(fieldId); if (isRequired != null && isRequired && value.isEmpty()) { - input.setError("Må fylles ut"); - input.requestFocus(); + if (view instanceof EditText) { + ((EditText) view).setError("Må fylles ut"); + view.requestFocus(); + } else if (view instanceof CheckBox) { + Toast.makeText(getContext(), "Du må bekrefte " + ((CheckBox)view).getText(), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getContext(), "Vennligst fyll ut alle obligatoriske felt.", Toast.LENGTH_SHORT).show(); + } return; } @@ -477,7 +785,6 @@ public class FormsFragment extends Fragment { } Request request = new Request.Builder().url(url).post(body).header("Cookie", cookie).build(); - client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { @@ -492,6 +799,9 @@ public class FormsFragment extends Fragment { Toast.makeText(getContext(), "Skjema sendt!", Toast.LENGTH_LONG).show(); fetchFormEntries(); updateStatus("Innsending OK"); + if (formId != ID_ANSATTEOPPLYSNINGER) { + clearInputs(); + } }); } } else { @@ -501,12 +811,20 @@ public class FormsFragment extends Fragment { }); } + private void clearInputs() { + for (View view : inputViews.values()) { + if (view instanceof EditText) ((EditText) view).setText(""); + if (view instanceof RadioGroup) ((RadioGroup) view).clearCheck(); + if (view instanceof Spinner) ((Spinner) view).setSelection(0); + if (view instanceof CheckBox) ((CheckBox) view).setChecked(false); + } + } + private void showHistory(JSONArray entries) { if (historyContainer == null) return; historyContainer.removeAllViews(); if (entries.length() == 0) { - // Skjul overskriften hvis det ikke er historikk if (lblHistory != null) lblHistory.setVisibility(View.GONE); return; } else { @@ -514,19 +832,19 @@ public class FormsFragment extends Fragment { } try { - for (int i = 0; i < entries.length(); i++) { + int count = Math.min(entries.length(), 5); + for (int i = 0; i < count; i++) { JSONObject entry = entries.getJSONObject(i); String date = entry.optString("date_created"); TextView item = new TextView(getContext()); - item.setText(date); + item.setText("Innsendt: " + date); item.setPadding(10, 20, 10, 20); - - // Gjør historikken klikkbar (for fremtidig funksjonalitet) item.setBackgroundResource(android.R.drawable.list_selector_background); View line = new View(getContext()); line.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); line.setBackgroundColor(Color.LTGRAY); + historyContainer.addView(item); historyContainer.addView(line); } diff --git a/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java index 9521f21..e5a61e2 100644 --- a/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java +++ b/app/src/main/java/com/kbs/kbsintranett/FormsListFragment.java @@ -2,6 +2,8 @@ package com.kbs.kbsintranett; import android.graphics.Color; import android.os.Bundle; +import android.text.Html; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,8 +17,13 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.navigation.Navigation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import retrofit2.Call; import retrofit2.Callback; @@ -24,6 +31,7 @@ import retrofit2.Response; public class FormsListFragment extends Fragment { + private static final String TAG = "FormsListFragment"; private LinearLayout container; private ProgressBar loadingSpinner; private TextView txtStatus; @@ -31,23 +39,16 @@ public class FormsListFragment extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - // Vi gjenbruker samme layout som detaljvisningen, siden den nå er ryddig View view = inflater.inflate(R.layout.fragment_forms, container, false); - this.container = view.findViewById(R.id.form_container); this.loadingSpinner = view.findViewById(R.id.loading_spinner); this.txtStatus = view.findViewById(R.id.txt_status); - // Skjul "Tidligere innsendinger" tekst og container på denne siden, - // da vi kun skal vise en meny her. - View historyTitle = view.findViewById(R.id.historyContainer); // Egentlig containeren, men vi skjuler alt under + View historyTitle = view.findViewById(R.id.historyContainer); if (historyTitle != null) historyTitle.setVisibility(View.GONE); - // Finn og skjul "Tidligere innsendinger" overskriften hvis mulig - // (Siden vi bruker en delt XML er dette litt "hacky", men funker) LinearLayout mainLayout = view.findViewById(R.id.main_layout); if (mainLayout != null && mainLayout.getChildCount() > 4) { - // Skjuler elementene nederst som hører til historikk for(int i = 0; i < mainLayout.getChildCount(); i++) { View child = mainLayout.getChildAt(i); if (child instanceof TextView && ((TextView)child).getText().toString().contains("Tidligere")) { @@ -70,34 +71,56 @@ public class FormsListFragment extends Fragment { if (loadingSpinner != null) loadingSpinner.setVisibility(View.VISIBLE); if (txtStatus != null) txtStatus.setText(""); - // Hent listen over skjemaer (Map ID -> Skjema) + Log.d(TAG, "Starter henting av skjemaer..."); + RetrofitClient.getApiService().getFormsListMap().enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { if (getContext() == null) return; - - // SKJUL SPINNER NÅR VI FÅR SVAR if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); if (response.isSuccessful() && response.body() != null) { Map formMap = response.body(); + Log.d(TAG, "Fant " + formMap.size() + " skjemaer totalt."); if (formMap.isEmpty()) { if (txtStatus != null) txtStatus.setText("Ingen skjemaer funnet."); return; } - // Vi sorterer listen basert på ID (eller tittel) for ryddighet - Map sortedMap = new TreeMap<>(formMap); + List visibleForms = new ArrayList<>(); + for (GravityForm form : formMap.values()) { + boolean isActive = form.isActive == null || !"0".equals(form.isActive); + Integer sortOrder = getSortOrderFromDescription(form); - for (Map.Entry entry : sortedMap.entrySet()) { - GravityForm form = entry.getValue(); - // Sjekk at skjemaet er aktivt og har en tittel - if (form != null && form.title != null) { - addFormButton(form); + // Logg hva vi finner for hvert skjema + Log.d(TAG, "Skjema: " + form.title + " | ID: " + form.id + " | Beskrivelse: " + form.description + " | Sorteringstall funnet: " + sortOrder); + + if (form != null && form.title != null && isActive && sortOrder != null) { + visibleForms.add(form); } } + + if (visibleForms.isEmpty()) { + Log.w(TAG, "Alle skjemaer ble filtrert bort. Sjekk at beskrivelsene starter med et tall."); + if (txtStatus != null) txtStatus.setText("Ingen tilgjengelige skjemaer i appen."); + return; + } + + // Sorter basert på tallet vi fant + Collections.sort(visibleForms, new Comparator() { + @Override + public int compare(GravityForm f1, GravityForm f2) { + return Integer.compare(getSortOrderFromDescription(f1), getSortOrderFromDescription(f2)); + } + }); + + for (GravityForm form : visibleForms) { + addFormButton(form); + } + } else { + Log.e(TAG, "Feil fra server: " + response.code()); if (txtStatus != null) txtStatus.setText("Kunne ikke laste listen (Kode " + response.code() + ")"); } } @@ -105,34 +128,59 @@ public class FormsListFragment extends Fragment { @Override public void onFailure(Call> call, Throwable t) { if (getContext() == null) return; - // SKJUL SPINNER VED FEIL if (loadingSpinner != null) loadingSpinner.setVisibility(View.GONE); + Log.e(TAG, "Nettverksfeil", t); if (txtStatus != null) txtStatus.setText("Nettverksfeil: " + t.getMessage()); } }); } + /** + * Leter etter et tall i starten av beskrivelsen. + * Håndterer HTML-tags og mellomrom. + */ + private Integer getSortOrderFromDescription(GravityForm form) { + if (form.description == null || form.description.isEmpty()) { + return null; // Skjules + } + + // 1. Fjern HTML-tags for å få ren tekst (f.eks "

10.

" -> "10.") + String cleanText = Html.fromHtml(form.description, Html.FROM_HTML_MODE_COMPACT).toString().trim(); + + // 2. Regex: Finn første sekvens av tall (\d+) + // ^\D* betyr "Start på linjen, ignorer ikke-tall (som bokstaver/tegn) i starten" + Pattern p = Pattern.compile("^\\D*(\\d+)"); + Matcher m = p.matcher(cleanText); + + if (m.find()) { + try { + return Integer.parseInt(m.group(1)); + } catch (NumberFormatException e) { + return null; + } + } + return null; // Ingen tall funnet -> Skjules + } + private void addFormButton(GravityForm form) { Button button = new Button(getContext()); - button.setText(form.title.toUpperCase()); // Store bokstaver ser ofte ryddigere ut i menyer + button.setText(form.title.toUpperCase()); button.setTextColor(Color.WHITE); button.setBackgroundColor(Color.parseColor("#0069B3")); // KBS Blå button.setTextSize(14); button.setPadding(30, 30, 30, 30); - // Klikk-lytter button.setOnClickListener(v -> { Bundle bundle = new Bundle(); bundle.putInt("formId", form.id); Navigation.findNavController(v).navigate(R.id.action_formsListFragment_to_formsDetailFragment, bundle); }); - // Layout parametere (Marginer) LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - params.setMargins(0, 0, 0, 20); // Avstand mellom knappene + params.setMargins(0, 0, 0, 20); button.setLayoutParams(params); container.addView(button); diff --git a/app/src/main/java/com/kbs/kbsintranett/GravityField.java b/app/src/main/java/com/kbs/kbsintranett/GravityField.java index a9f70d9..ae9e738 100644 --- a/app/src/main/java/com/kbs/kbsintranett/GravityField.java +++ b/app/src/main/java/com/kbs/kbsintranett/GravityField.java @@ -14,6 +14,11 @@ public class GravityField { @SerializedName("label") public String label; + // --- DETTE ER FELTET SOM MANGLER --- + @SerializedName("adminLabel") + public String adminLabel; + // ----------------------------------- + @SerializedName("description") public String description; @@ -46,7 +51,7 @@ public class GravityField { @SerializedName("conditionalLogic") public ConditionalLogic conditionalLogic; - // NYTT: For å lese Populate Anything-regler + // For å lese Populate Anything-regler @SerializedName("gppa-values-templates") public java.util.Map gppaTemplates; diff --git a/app/src/main/java/com/kbs/kbsintranett/GravityForm.java b/app/src/main/java/com/kbs/kbsintranett/GravityForm.java index cd54690..da613d0 100644 --- a/app/src/main/java/com/kbs/kbsintranett/GravityForm.java +++ b/app/src/main/java/com/kbs/kbsintranett/GravityForm.java @@ -13,6 +13,9 @@ public class GravityForm { @SerializedName("description") public String description; + @SerializedName("is_active") + public String isActive; // "1" = Aktiv, "0" = Inaktiv + @SerializedName("fields") - public List fields; // En liste med alle feltene i skjemaet + public List fields; } \ No newline at end of file