library(readxl)       
library(dplyr)        
## Warning: package 'dplyr' was built under R version 4.5.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)      
## Warning: package 'tidyr' was built under R version 4.5.3
library(mice)         
## Warning: package 'mice' was built under R version 4.5.3
## 
## Attaching package: 'mice'
## The following object is masked from 'package:stats':
## 
##     filter
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
library(MVN)          
## Warning: package 'MVN' was built under R version 4.5.3
library(psych)        
## 
## Attaching package: 'psych'
## The following object is masked from 'package:MVN':
## 
##     mardia
library(lavaan)
## Warning: package 'lavaan' was built under R version 4.5.3
## This is lavaan 0.6-21
## lavaan is FREE software! Please report any bugs.
## 
## Attaching package: 'lavaan'
## The following object is masked from 'package:psych':
## 
##     cor2cov
library(semPlot)      
## Warning: package 'semPlot' was built under R version 4.5.3
library(semTools)     
## Warning: package 'semTools' was built under R version 4.5.3
## 
## ###############################################################################
## This is semTools 0.5-8
## All users of R (or SEM) are invited to submit functions or ideas for functions.
## ###############################################################################
## 
## Attaching package: 'semTools'
## The following objects are masked from 'package:psych':
## 
##     reliability, skew
library(ggplot2)      
## Warning: package 'ggplot2' was built under R version 4.5.3
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
library(corrplot)
## corrplot 0.95 loaded
library(flextable)    
## Warning: package 'flextable' was built under R version 4.5.3
library(openxlsx)     
## Warning: package 'openxlsx' was built under R version 4.5.3
cat("\n========== TAHAP 1: PERSIAPAN & SELEKSI VARIABEL ==========\n")
## 
## ========== TAHAP 1: PERSIAPAN & SELEKSI VARIABEL ==========
# --- 1.1 Load data -----------------------------------------------------------
df_raw <- read_excel("data6.xlsx", sheet = "Data")
cat("Dimensi data raw:", nrow(df_raw), "baris x", ncol(df_raw), "kolom\n")
## Dimensi data raw: 200 baris x 255 kolom
indicators <- list(
  JOB_DEMANDS   = paste0("JDR", 11:20),       # 10 item demands
  JOB_RESOURCES = paste0("JDR", 21:30),       # 10 item resources
  WELLBEING     = paste0("FW",   1:10),        # 10 item wellbeing
  WORK_ENGAGE   = paste0("WMI",  1:10),        # 10 item engagement
  ITL           = c("ITL1", "ITL2", "ITL3"),  # 3  item intention to leave
  JOB_PERF      = c("JP1", "JP2", "JP3", "JP4") # 4  item job performance
)

all_items <- unlist(indicators, use.names = FALSE)
cat("Total item dipilih:", length(all_items), "\n")
## Total item dipilih: 47
cat("Konstruk:", paste(names(indicators), collapse = ", "), "\n")
## Konstruk: JOB_DEMANDS, JOB_RESOURCES, WELLBEING, WORK_ENGAGE, ITL, JOB_PERF
# --- 1.3 Subset data ---------------------------------------------------------
df <- df_raw %>%
  select(No, Gender, Age, all_of(all_items)) %>%
  mutate(across(all_of(all_items), as.numeric))

cat("Dimensi data subset:", nrow(df), "baris x", ncol(df), "kolom\n")
## Dimensi data subset: 200 baris x 50 kolom
# --- 1.4 Statistik deskriptif awal -------------------------------------------
cat("\n--- Statistik Deskriptif Awal ---\n")
## 
## --- Statistik Deskriptif Awal ---
desc_stats <- df %>%
  select(all_of(all_items)) %>%
  describe() %>%                        # dari package psych
  round(3)

print(desc_stats[, c("n", "mean", "sd", "min", "max", "skew", "kurtosis")])
##         n mean   sd min max  skew kurtosis
## JDR11 200 3.86 0.97   1   5 -0.34    -0.57
## JDR12 197 3.46 1.14   1   5 -0.34    -0.61
## JDR13 200 3.69 1.08   1   5 -0.40    -0.50
## JDR14 199 3.72 1.03   1   5 -0.54    -0.15
## JDR15 198 4.31 0.82   2   5 -1.00     0.21
## JDR16 199 4.10 1.00   1   5 -0.89     0.18
## JDR17 199 2.77 1.29   1   5  0.17    -1.00
## JDR18 200 3.07 1.28   1   5 -0.12    -1.01
## JDR19 197 2.81 1.17   1   5  0.08    -0.70
## JDR20 200 3.69 1.10   1   5 -0.60    -0.24
## JDR21 200 4.04 1.01   1   5 -0.77    -0.19
## JDR22 200 4.04 0.97   1   5 -0.91     0.44
## JDR23 199 4.32 0.85   1   5 -1.20     1.23
## JDR24 200 4.07 1.00   1   5 -0.93     0.29
## JDR25 200 3.95 1.01   1   5 -0.54    -0.65
## JDR26 199 4.00 1.18   1   5 -0.97    -0.02
## JDR27 200 4.32 0.93   1   5 -1.23     0.80
## JDR28 199 3.92 1.14   1   5 -0.77    -0.36
## JDR29 199 3.86 1.03   1   5 -0.56    -0.52
## JDR30 199 4.35 0.83   1   5 -1.19     0.98
## FW1   200 4.74 1.02   1   6 -1.47     2.92
## FW2   199 4.54 1.11   1   6 -1.13     1.32
## FW3   199 5.04 1.03   1   6 -1.39     2.48
## FW4   200 2.75 1.31   1   6  0.73    -0.18
## FW5   200 1.99 1.23   1   6  1.46     1.75
## FW6   200 2.02 1.25   1   6  1.25     0.95
## FW7   199 4.54 1.29   1   6 -1.18     0.96
## FW8   200 4.72 1.30   1   6 -1.22     0.98
## FW9   199 4.97 0.97   1   6 -1.05     1.26
## FW10  195 4.58 1.12   1   6 -0.94     0.49
## WMI1  175 4.37 0.80   1   5 -1.49     2.87
## WMI2  198 4.28 0.98   1   6 -1.66     2.80
## WMI3  198 4.11 1.45   1   5 -1.35     0.17
## WMI4  198 4.37 0.91   1   5 -1.93     4.13
## WMI5  198 4.48 0.80   1   6 -2.13     6.00
## WMI6  196 4.59 0.82   1   6 -2.27     6.57
## WMI7  198 4.37 0.86   1   6 -1.79     4.38
## WMI8  197 4.26 0.95   1   6 -1.39     2.14
## WMI9  198 4.35 0.86   1   6 -1.45     2.94
## WMI10 198 4.59 0.79   1   6 -2.18     6.48
## ITL1  199 2.43 1.30   1   6  0.65    -0.44
## ITL2  199 2.14 1.08   1   5  0.81     0.15
## ITL3  199 2.28 1.26   1   5  0.76    -0.44
## JP1   194 7.47 1.66   1  10 -0.89     1.54
## JP2   194 7.65 1.68   1  10 -0.72     0.38
## JP3   195 7.80 1.55   1  10 -0.78     0.98
## JP4   195 7.91 1.65   1  10 -1.17     1.81
# Simpan ke Excel
wb <- createWorkbook()
addWorksheet(wb, "Deskriptif")
writeData(wb, "Deskriptif", desc_stats, rowNames = TRUE)
# =============================================================================
# TAHAP 2: PEMBERSIHAN DATA & MISSING VALUES
# =============================================================================
cat("\n========== TAHAP 2: PEMBERSIHAN DATA & MISSING VALUES ==========\n")
## 
## ========== TAHAP 2: PEMBERSIHAN DATA & MISSING VALUES ==========
# --- 2.1 Identifikasi missing values -----------------------------------------
df_items <- df %>% select(all_of(all_items))

missing_summary <- data.frame(
  Variabel  = names(df_items),
  N_Missing = colSums(is.na(df_items)),
  Pct_Missing = round(colMeans(is.na(df_items)) * 100, 2)
) %>% filter(N_Missing > 0) %>% arrange(desc(Pct_Missing))

cat("Variabel dengan missing values:\n")
## Variabel dengan missing values:
print(missing_summary)
##       Variabel N_Missing Pct_Missing
## WMI1      WMI1        25        12.5
## JP1        JP1         6         3.0
## JP2        JP2         6         3.0
## FW10      FW10         5         2.5
## JP3        JP3         5         2.5
## JP4        JP4         5         2.5
## WMI6      WMI6         4         2.0
## JDR12    JDR12         3         1.5
## JDR19    JDR19         3         1.5
## WMI8      WMI8         3         1.5
## JDR15    JDR15         2         1.0
## WMI2      WMI2         2         1.0
## WMI3      WMI3         2         1.0
## WMI4      WMI4         2         1.0
## WMI5      WMI5         2         1.0
## WMI7      WMI7         2         1.0
## WMI9      WMI9         2         1.0
## WMI10    WMI10         2         1.0
## JDR14    JDR14         1         0.5
## JDR16    JDR16         1         0.5
## JDR17    JDR17         1         0.5
## JDR23    JDR23         1         0.5
## JDR26    JDR26         1         0.5
## JDR28    JDR28         1         0.5
## JDR29    JDR29         1         0.5
## JDR30    JDR30         1         0.5
## FW2        FW2         1         0.5
## FW3        FW3         1         0.5
## FW7        FW7         1         0.5
## FW9        FW9         1         0.5
## ITL1      ITL1         1         0.5
## ITL2      ITL2         1         0.5
## ITL3      ITL3         1         0.5
cat("Total missing:", sum(is.na(df_items)), "dari",
    nrow(df_items) * ncol(df_items), "sel (",
    round(mean(is.na(df_items)) * 100, 2), "%)\n")
## Total missing: 96 dari 9400 sel ( 1.02 %)
# Visualisasi pola missing
missing_pct_per_row <- rowMeans(is.na(df_items)) * 100
cat("Baris dengan ANY missing:", sum(rowMeans(is.na(df_items)) > 0), "\n")
## Baris dengan ANY missing: 25
cat("Baris dengan > 10% missing:", sum(missing_pct_per_row > 10), "\n")
## Baris dengan > 10% missing: 5
# --- 2.2 Multiple Imputation dengan MICE -------------------------------------
#  Metode: pmm (predictive mean matching) — cocok untuk data Likert ordinal
#  m = 5 → buat 5 dataset imputasi, lalu pool hasilnya
cat("\nMelakukan Multiple Imputation (MICE)...\n")
## 
## Melakukan Multiple Imputation (MICE)...
set.seed(42)

imp_result <- mice(
  data    = df_items,
  m       = 5,          # 5 dataset imputasi
  method  = "pmm",      # predictive mean matching
  maxit   = 10,         # iterasi maksimal
  printFlag = FALSE
)

# Ambil dataset komplit pertama (untuk analisis awal)
# Untuk analisis final sebaiknya gunakan pool() dari semua imputasi
df_clean <- complete(imp_result, action = 1)

# Bulatkan ke integer (karena Likert adalah ordinal)
df_clean <- df_clean %>% mutate(across(everything(), ~ round(.x)))

cat("\nNormalisasi JP1-JP4 dari skala 1-10 ke 1-5...\n")
## 
## Normalisasi JP1-JP4 dari skala 1-10 ke 1-5...
jp_items <- c("JP1", "JP2", "JP3", "JP4")
df_clean[, jp_items] <- lapply(df_clean[, jp_items], function(x) {
  x_norm <- ceiling((x / 10) * 5)
  x_norm <- pmax(1, pmin(5, x_norm))  # clamp ke [1,5]
  x_norm
})
cat("Range JP setelah normalisasi:\n")
## Range JP setelah normalisasi:
print(sapply(df_clean[, jp_items], range))
##      JP1 JP2 JP3 JP4
## [1,]   1   1   1   1
## [2,]   5   5   5   5
# Verifikasi: tidak ada missing lagi
cat("Missing setelah imputasi:", sum(is.na(df_clean)), "\n")
## Missing setelah imputasi: 0
# --- 2.3 Deteksi outlier multivariat (Mahalanobis distance) ------------------
cat("\nDeteksi outlier dengan Mahalanobis distance...\n")
## 
## Deteksi outlier dengan Mahalanobis distance...
mah_dist <- mahalanobis(
  x      = df_clean,
  center = colMeans(df_clean),
  cov    = cov(df_clean)
)

# Threshold: chi-square p < 0.001 dengan df = jumlah variabel
df_mah     <- ncol(df_clean)
threshold  <- qchisq(0.999, df = df_mah)
outlier_idx <- which(mah_dist > threshold)

cat("Jumlah outlier multivariat:", length(outlier_idx), "\n")
## Jumlah outlier multivariat: 6
if (length(outlier_idx) > 0) {
  cat("Indeks baris outlier:", outlier_idx, "\n")
}
## Indeks baris outlier: 6 66 81 110 166 190
# Simpan data bersih
addWorksheet(wb, "Data_Clean")
writeData(wb, "Data_Clean", df_clean)
# =============================================================================
# TAHAP 3: UJI ASUMSI
# =============================================================================
cat("\n========== TAHAP 3: UJI ASUMSI ==========\n")
## 
## ========== TAHAP 3: UJI ASUMSI ==========
# --- 3.1 Uji Normalitas (tanpa bergantung versi MVN) ---
cat("\n--- Uji Normalitas Multivariat ---\n")
## 
## --- Uji Normalitas Multivariat ---
wellbeing_data <- df_clean[, indicators$WELLBEING]

# Shapiro-Wilk per indikator (univariat)
cat("\nHasil Shapiro-Wilk per indikator WELLBEING:\n")
## 
## Hasil Shapiro-Wilk per indikator WELLBEING:
sw_results <- sapply(wellbeing_data, function(x) {
  test <- shapiro.test(x)
  c(W = round(test$statistic, 4), p.value = round(test$p.value, 4))
})
print(t(sw_results))
##         W.W p.value
## FW1  0.7907       0
## FW2  0.8403       0
## FW3  0.7984       0
## FW4  0.8872       0
## FW5  0.7692       0
## FW6  0.7881       0
## FW7  0.8301       0
## FW8  0.8191       0
## FW9  0.8318       0
## FW10 0.8607       0
# Mardia's test manual via psych package (lebih stabil)
if (!requireNamespace("psych", quietly = TRUE)) install.packages("psych")
library(psych)

mardia_result <- mardia(wellbeing_data, plot = FALSE)
cat("\nMardia's Skewness:\n")
## 
## Mardia's Skewness:
cat(sprintf("  b1p = %.4f, chi2 = %.4f, p = %.4f\n",
    mardia_result$b1p,
    mardia_result$chi.skew,
    mardia_result$p.skew))

cat("\nMardia's Kurtosis:\n")
## 
## Mardia's Kurtosis:
cat(sprintf("  b2p = %.4f, z = %.4f, p = %.4f\n",
    mardia_result$b2p,
    mardia_result$kurtosis,
    mardia_result$p.kurt))
##   b2p = 162.4572, z = 19.3790, p = 0.0000
# Interpretasi otomatis
if (mardia_result$p.skew < 0.05 | mardia_result$p.kurt < 0.05) {
  cat("\n⚠ Normalitas multivariat TIDAK terpenuhi → Estimator MLR sudah tepat digunakan.\n")
} else {
  cat("\n✓ Normalitas multivariat terpenuhi (p > 0.05).\n")
}
## 
## ⚠ Normalitas multivariat TIDAK terpenuhi → Estimator MLR sudah tepat digunakan.
cat("\nCatatan: Script ini menggunakan estimator MLR (robust ML)\n")
## 
## Catatan: Script ini menggunakan estimator MLR (robust ML)
cat("yang tidak mengasumsikan normalitas multivariat sempurna.\n")
## yang tidak mengasumsikan normalitas multivariat sempurna.
# --- 3.2 Heatmap korelasi antar konstruk ------------------------------------
cat("\n--- Korelasi Antar Konstruk ---\n")
## 
## --- Korelasi Antar Konstruk ---
# Hitung rata-rata per konstruk sebagai skor komposit sementara
composite_scores <- data.frame(
  JOB_DEMANDS   = rowMeans(df_clean[, indicators$JOB_DEMANDS]),
  JOB_RESOURCES = rowMeans(df_clean[, indicators$JOB_RESOURCES]),
  WELLBEING     = rowMeans(df_clean[, indicators$WELLBEING]),
  WORK_ENGAGE   = rowMeans(df_clean[, indicators$WORK_ENGAGE]),
  ITL           = rowMeans(df_clean[, indicators$ITL]),
  JOB_PERF      = rowMeans(df_clean[, indicators$JOB_PERF])
)

cor_matrix <- cor(composite_scores, use = "pairwise.complete.obs")
cat("Matriks korelasi antar konstruk:\n")
## Matriks korelasi antar konstruk:
print(round(cor_matrix, 3))
##               JOB_DEMANDS JOB_RESOURCES WELLBEING WORK_ENGAGE    ITL JOB_PERF
## JOB_DEMANDS         1.000         0.027    -0.026      -0.028  0.060    0.073
## JOB_RESOURCES       0.027         1.000     0.190       0.143 -0.104    0.076
## WELLBEING          -0.026         0.190     1.000       0.025 -0.116    0.011
## WORK_ENGAGE        -0.028         0.143     0.025       1.000 -0.129    0.026
## ITL                 0.060        -0.104    -0.116      -0.129  1.000    0.080
## JOB_PERF            0.073         0.076     0.011       0.026  0.080    1.000
# Plot heatmap korelasi
png("plot_korelasi_konstruk.png", width = 800, height = 700, res = 120)
corrplot(
  cor_matrix,
  method    = "color",
  type      = "upper",
  addCoef.col = "black",
  number.cex  = 0.8,
  tl.cex    = 0.9,
  tl.col    = "black",
  col       = colorRampPalette(c("#3B8BD4", "white", "#D85A30"))(200),
  title     = "Korelasi Antar Konstruk",
  mar       = c(0, 0, 2, 0)
)
dev.off()
## png 
##   2
cat("Plot korelasi disimpan: plot_korelasi_konstruk.png\n")
## Plot korelasi disimpan: plot_korelasi_konstruk.png
# --- 3.3 Uji multikolinearitas (VIF) ----------------------------------------
cat("\n--- Cek Multikolinearitas ---\n")
## 
## --- Cek Multikolinearitas ---
# Korelasi > 0.85 antar konstruk mengindikasikan masalah diskriminan
high_cor <- which(abs(cor_matrix) > 0.85 & cor_matrix != 1, arr.ind = TRUE)
if (nrow(high_cor) > 0) {
  cat("PERHATIAN: Pasangan konstruk dengan korelasi > 0.85:\n")
  print(high_cor)
} else {
  cat("Tidak ada masalah multikolinearitas (semua korelasi < 0.85)\n")
}
## Tidak ada masalah multikolinearitas (semua korelasi < 0.85)
# Simpan matriks korelasi
addWorksheet(wb, "Korelasi_Konstruk")
writeData(wb, "Korelasi_Konstruk", as.data.frame(round(cor_matrix, 3)),
          rowNames = TRUE)
# =============================================================================
# TAHAP 4: MEASUREMENT MODEL (CFA)
# =============================================================================
cat("\n========== TAHAP 4: MEASUREMENT MODEL (CFA) ==========\n")
## 
## ========== TAHAP 4: MEASUREMENT MODEL (CFA) ==========
# --- 4.1 Spesifikasi model CFA -----------------------------------------------
model_cfa <- '
  # Konstruk 1: Job Demands (item JDR11-JDR20)
  JOB_DEMANDS   =~ JDR11 + JDR12 + JDR13 + JDR14 + JDR15 +
                   JDR16 + JDR17 + JDR18 + JDR19 + JDR20

  # Konstruk 2: Job Resources (item JDR21-JDR30)
  JOB_RESOURCES =~ JDR21 + JDR22 + JDR23 + JDR24 + JDR25 +
                   JDR26 + JDR27 + JDR28 + JDR29 + JDR30

  # Konstruk 3: Wellbeing (item FW1-FW10)
  WELLBEING     =~ FW1 + FW2 + FW3 + FW4 + FW5 +
                   FW6 + FW7 + FW8 + FW9 + FW10

  # Konstruk 4: Work Engagement (item WMI1-WMI10)
  WORK_ENGAGE   =~ WMI1 + WMI2 + WMI3 + WMI4 + WMI5 +
                   WMI6 + WMI7 + WMI8 + WMI9 + WMI10

  # Konstruk 5: Intention to Leave (item ITL1-ITL3)
  ITL           =~ ITL1 + ITL2 + ITL3

  # Konstruk 6: Job Performance (item JP1-JP4)
  JOB_PERF      =~ JP1 + JP2 + JP3 + JP4
'

# --- 4.2 Estimasi CFA --------------------------------------------------------
cat("Mengestimasi model CFA...\n")
## Mengestimasi model CFA...
fit_cfa <- cfa(
  model     = model_cfa,
  data      = df_clean,
  estimator = "MLR",
  std.lv    = TRUE,          # <-- ubah ini
  missing   = "fiml",
  control   = list(iter.max = 10000)
)

cfa_converged <- lavInspect(fit_cfa, "converged")
cat("CFA converged:", cfa_converged, "\n")
## CFA converged: TRUE
if (!cfa_converged) {
  stop("Model CFA tidak konvergen. Periksa spesifikasi model atau data.")
}

# --- 4.3 Evaluasi fit CFA ----------------------------------------------------
cat("\n--- Indeks Fit CFA ---\n")
## 
## --- Indeks Fit CFA ---
fit_indices_cfa <- fitMeasures(fit_cfa, c(
  "chisq.scaled", "df.scaled", "pvalue.scaled",
  "cfi.scaled", "tli.scaled",
  "rmsea.scaled", "rmsea.ci.lower.scaled", "rmsea.ci.upper.scaled",
  "srmr"
))
print(round(fit_indices_cfa, 4))
##          chisq.scaled             df.scaled         pvalue.scaled 
##              1835.531              1019.000                 0.000 
##            cfi.scaled            tli.scaled          rmsea.scaled 
##                 0.661                 0.640                 0.063 
## rmsea.ci.lower.scaled rmsea.ci.upper.scaled                  srmr 
##                 0.059                 0.068                 0.083
# Evaluasi berdasarkan kriteria
cat("\n--- Evaluasi Fit ---\n")
## 
## --- Evaluasi Fit ---
cfi_val   <- fit_indices_cfa["cfi.scaled"]
rmsea_val <- fit_indices_cfa["rmsea.scaled"]
srmr_val  <- fit_indices_cfa["srmr"]

cat("CFI  :", round(cfi_val, 3),
    ifelse(cfi_val >= 0.95, "[EXCELLENT]",
    ifelse(cfi_val >= 0.90, "[ACCEPTABLE]", "[POOR - perlu revisi]")), "\n")
## CFI  : 0.661 [POOR - perlu revisi]
cat("RMSEA:", round(rmsea_val, 3),
    ifelse(rmsea_val <= 0.06, "[EXCELLENT]",
    ifelse(rmsea_val <= 0.08, "[ACCEPTABLE]", "[POOR - perlu revisi]")), "\n")
## RMSEA: 0.063 [ACCEPTABLE]
cat("SRMR :", round(srmr_val, 3),
    ifelse(srmr_val <= 0.08, "[ACCEPTABLE]", "[POOR - perlu revisi]"), "\n")
## SRMR : 0.083 [POOR - perlu revisi]
# --- 4.4 Factor loadings (standardized) -------------------------------------
cat("\n--- Factor Loadings (Standardized) ---\n")
## 
## --- Factor Loadings (Standardized) ---
params_cfa <- parameterEstimates(fit_cfa, standardized = TRUE) %>%
  filter(op == "=~") %>%
  select(Konstruk = lhs, Indikator = rhs,
         Loading_Std = std.all, SE = se,
         z = z, pvalue = pvalue) %>%
  mutate(across(where(is.numeric), ~ round(.x, 3)),
         Signifikan = ifelse(pvalue < 0.05, "Ya", "Tidak"),
         Evaluasi   = ifelse(abs(Loading_Std) >= 0.50, "OK (>= 0.5)",
                                                       "Rendah (< 0.5)"))

print(params_cfa)
##         Konstruk Indikator Loading_Std    SE      z pvalue Signifikan
## 1    JOB_DEMANDS     JDR11       0.496 0.099  4.848  0.000         Ya
## 2    JOB_DEMANDS     JDR12       0.057 0.103  0.634  0.526      Tidak
## 3    JOB_DEMANDS     JDR13       0.468 0.155  3.245  0.001         Ya
## 4    JOB_DEMANDS     JDR14       0.332 0.111  3.109  0.002         Ya
## 5    JOB_DEMANDS     JDR15       0.083 0.088  0.782  0.434      Tidak
## 6    JOB_DEMANDS     JDR16       0.433 0.120  3.605  0.000         Ya
## 7    JOB_DEMANDS     JDR17       0.442 0.142  4.009  0.000         Ya
## 8    JOB_DEMANDS     JDR18       0.654 0.166  5.027  0.000         Ya
## 9    JOB_DEMANDS     JDR19       0.363 0.111  3.840  0.000         Ya
## 10   JOB_DEMANDS     JDR20      -0.168 0.121 -1.535  0.125      Tidak
## 11 JOB_RESOURCES     JDR21       0.621 0.086  7.274  0.000         Ya
## 12 JOB_RESOURCES     JDR22       0.782 0.076 10.034  0.000         Ya
## 13 JOB_RESOURCES     JDR23       0.365 0.074  4.197  0.000         Ya
## 14 JOB_RESOURCES     JDR24       0.582 0.075  7.797  0.000         Ya
## 15 JOB_RESOURCES     JDR25       0.639 0.058 11.076  0.000         Ya
## 16 JOB_RESOURCES     JDR26       0.016 0.107  0.173  0.862      Tidak
## 17 JOB_RESOURCES     JDR27       0.638 0.070  8.395  0.000         Ya
## 18 JOB_RESOURCES     JDR28       0.381 0.112  3.908  0.000         Ya
## 19 JOB_RESOURCES     JDR29       0.215 0.086  2.569  0.010         Ya
## 20 JOB_RESOURCES     JDR30       0.445 0.076  4.858  0.000         Ya
## 21     WELLBEING       FW1       0.650 0.105  6.308  0.000         Ya
## 22     WELLBEING       FW2       0.355 0.104  3.777  0.000         Ya
## 23     WELLBEING       FW3       0.302 0.101  3.066  0.002         Ya
## 24     WELLBEING       FW4      -0.547 0.118 -6.049  0.000         Ya
## 25     WELLBEING       FW5      -0.777 0.115 -8.240  0.000         Ya
## 26     WELLBEING       FW6      -0.583 0.108 -6.756  0.000         Ya
## 27     WELLBEING       FW7       0.259 0.134  2.537  0.011         Ya
## 28     WELLBEING       FW8       0.432 0.131  4.299  0.000         Ya
## 29     WELLBEING       FW9       0.317 0.092  3.416  0.001         Ya
## 30     WELLBEING      FW10       0.068 0.090  0.873  0.383      Tidak
## 31   WORK_ENGAGE      WMI1       0.026 0.057  0.361  0.718      Tidak
## 32   WORK_ENGAGE      WMI2      -0.386 0.098 -3.834  0.000         Ya
## 33   WORK_ENGAGE      WMI3      -0.082 0.098 -1.212  0.226      Tidak
## 34   WORK_ENGAGE      WMI4      -0.699 0.090 -7.017  0.000         Ya
## 35   WORK_ENGAGE      WMI5      -0.784 0.094 -6.705  0.000         Ya
## 36   WORK_ENGAGE      WMI6      -0.241 0.103 -1.908  0.056      Tidak
## 37   WORK_ENGAGE      WMI7      -0.848 0.091 -7.953  0.000         Ya
## 38   WORK_ENGAGE      WMI8      -0.224 0.105 -2.008  0.045         Ya
## 39   WORK_ENGAGE      WMI9      -0.763 0.082 -7.905  0.000         Ya
## 40   WORK_ENGAGE     WMI10      -0.656 0.091 -5.666  0.000         Ya
## 41           ITL      ITL1       0.714 0.087 10.629  0.000         Ya
## 42           ITL      ITL2       0.949 0.070 14.664  0.000         Ya
## 43           ITL      ITL3       0.796 0.072 13.898  0.000         Ya
## 44      JOB_PERF       JP1       0.270 0.067  3.118  0.002         Ya
## 45      JOB_PERF       JP2       0.331 0.080  3.304  0.001         Ya
## 46      JOB_PERF       JP3       0.794 0.065  9.411  0.000         Ya
## 47      JOB_PERF       JP4       0.899 0.072 10.461  0.000         Ya
##          Evaluasi
## 1  Rendah (< 0.5)
## 2  Rendah (< 0.5)
## 3  Rendah (< 0.5)
## 4  Rendah (< 0.5)
## 5  Rendah (< 0.5)
## 6  Rendah (< 0.5)
## 7  Rendah (< 0.5)
## 8     OK (>= 0.5)
## 9  Rendah (< 0.5)
## 10 Rendah (< 0.5)
## 11    OK (>= 0.5)
## 12    OK (>= 0.5)
## 13 Rendah (< 0.5)
## 14    OK (>= 0.5)
## 15    OK (>= 0.5)
## 16 Rendah (< 0.5)
## 17    OK (>= 0.5)
## 18 Rendah (< 0.5)
## 19 Rendah (< 0.5)
## 20 Rendah (< 0.5)
## 21    OK (>= 0.5)
## 22 Rendah (< 0.5)
## 23 Rendah (< 0.5)
## 24    OK (>= 0.5)
## 25    OK (>= 0.5)
## 26    OK (>= 0.5)
## 27 Rendah (< 0.5)
## 28 Rendah (< 0.5)
## 29 Rendah (< 0.5)
## 30 Rendah (< 0.5)
## 31 Rendah (< 0.5)
## 32 Rendah (< 0.5)
## 33 Rendah (< 0.5)
## 34    OK (>= 0.5)
## 35    OK (>= 0.5)
## 36 Rendah (< 0.5)
## 37    OK (>= 0.5)
## 38 Rendah (< 0.5)
## 39    OK (>= 0.5)
## 40    OK (>= 0.5)
## 41    OK (>= 0.5)
## 42    OK (>= 0.5)
## 43    OK (>= 0.5)
## 44 Rendah (< 0.5)
## 45 Rendah (< 0.5)
## 46    OK (>= 0.5)
## 47    OK (>= 0.5)
# Tandai item dengan loading rendah
low_loading <- params_cfa %>% filter(abs(Loading_Std) < 0.50)
if (nrow(low_loading) > 0) {
  cat("\nPERHATIAN - Item dengan loading < 0.50 (pertimbangkan dihapus):\n")
  print(low_loading %>% select(Konstruk, Indikator, Loading_Std))
}
## 
## PERHATIAN - Item dengan loading < 0.50 (pertimbangkan dihapus):
##         Konstruk Indikator Loading_Std
## 1    JOB_DEMANDS     JDR11       0.496
## 2    JOB_DEMANDS     JDR12       0.057
## 3    JOB_DEMANDS     JDR13       0.468
## 4    JOB_DEMANDS     JDR14       0.332
## 5    JOB_DEMANDS     JDR15       0.083
## 6    JOB_DEMANDS     JDR16       0.433
## 7    JOB_DEMANDS     JDR17       0.442
## 8    JOB_DEMANDS     JDR19       0.363
## 9    JOB_DEMANDS     JDR20      -0.168
## 10 JOB_RESOURCES     JDR23       0.365
## 11 JOB_RESOURCES     JDR26       0.016
## 12 JOB_RESOURCES     JDR28       0.381
## 13 JOB_RESOURCES     JDR29       0.215
## 14 JOB_RESOURCES     JDR30       0.445
## 15     WELLBEING       FW2       0.355
## 16     WELLBEING       FW3       0.302
## 17     WELLBEING       FW7       0.259
## 18     WELLBEING       FW8       0.432
## 19     WELLBEING       FW9       0.317
## 20     WELLBEING      FW10       0.068
## 21   WORK_ENGAGE      WMI1       0.026
## 22   WORK_ENGAGE      WMI2      -0.386
## 23   WORK_ENGAGE      WMI3      -0.082
## 24   WORK_ENGAGE      WMI6      -0.241
## 25   WORK_ENGAGE      WMI8      -0.224
## 26      JOB_PERF       JP1       0.270
## 27      JOB_PERF       JP2       0.331
# --- 4.5 Reliabilitas & validitas konstruk ----------------------------------
cat("\n--- Composite Reliability (CR) & Average Variance Extracted (AVE) ---\n")
## 
## --- Composite Reliability (CR) & Average Variance Extracted (AVE) ---
# Hitung CR dan AVE secara manual dari factor loadings
hitung_cr_ave <- function(loadings) {
  lam    <- loadings                    # factor loadings
  err    <- 1 - lam^2                  # error variance
  cr     <- sum(lam)^2 / (sum(lam)^2 + sum(err))
  ave    <- sum(lam^2) / (sum(lam^2) + sum(err))
  return(c(CR = round(cr, 3), AVE = round(ave, 3)))
}

std_loads <- parameterEstimates(fit_cfa, standardized = TRUE) %>%
  filter(op == "=~") %>%
  select(lhs, rhs, std.all)

reliability_table <- std_loads %>%
  group_by(Konstruk = lhs) %>%
  summarise(
    N_Item = n(),
    CR     = hitung_cr_ave(std.all)["CR"],
    AVE    = hitung_cr_ave(std.all)["AVE"],
    .groups = "drop"
  ) %>%
  mutate(
    CR_OK  = ifelse(CR >= 0.70, "OK (>= 0.7)", "Rendah"),
    AVE_OK = ifelse(AVE >= 0.50, "OK (>= 0.5)", "Rendah")
  )

print(reliability_table)
## # A tibble: 6 × 6
##   Konstruk      N_Item    CR   AVE CR_OK       AVE_OK     
##   <chr>          <int> <dbl> <dbl> <chr>       <chr>      
## 1 ITL                3 0.864 0.681 OK (>= 0.7) OK (>= 0.5)
## 2 JOB_DEMANDS       10 0.542 0.156 Rendah      Rendah     
## 3 JOB_PERF           4 0.689 0.405 Rendah      Rendah     
## 4 JOB_RESOURCES     10 0.75  0.267 OK (>= 0.7) Rendah     
## 5 WELLBEING         10 0.028 0.224 Rendah      Rendah     
## 6 WORK_ENGAGE       10 0.759 0.31  OK (>= 0.7) Rendah
# --- 4.6 Discriminant validity (HTMT / sqrt AVE) ----------------------------
cat("\n--- Discriminant Validity (sqrt AVE vs korelasi konstruk) ---\n")
## 
## --- Discriminant Validity (sqrt AVE vs korelasi konstruk) ---
# Dapatkan korelasi konstruk dari CFA
cor_latent <- lavInspect(fit_cfa, what = "cor.lv")
sqrt_ave   <- sqrt(reliability_table$AVE)
names(sqrt_ave) <- reliability_table$Konstruk

cat("sqrt(AVE) per konstruk:\n")
## sqrt(AVE) per konstruk:
print(round(sqrt_ave, 3))
##           ITL   JOB_DEMANDS      JOB_PERF JOB_RESOURCES     WELLBEING 
##         0.825         0.395         0.636         0.517         0.473 
##   WORK_ENGAGE 
##         0.557
cat("\nKorelasi antar konstruk laten:\n")
## 
## Korelasi antar konstruk laten:
print(round(cor_latent, 3))
##               JOB_DE JOB_RE WELLBE WORK_E    ITL JOB_PE
## JOB_DEMANDS    1.000                                   
## JOB_RESOURCES -0.133  1.000                            
## WELLBEING     -0.354  0.572  1.000                     
## WORK_ENGAGE   -0.003 -0.122 -0.103  1.000              
## ITL            0.115 -0.051 -0.171  0.169  1.000       
## JOB_PERF       0.102  0.106  0.099 -0.057  0.129  1.000
cat("\nDiscriminant validity terpenuhi jika sqrt(AVE) > korelasi dengan konstruk lain\n")
## 
## Discriminant validity terpenuhi jika sqrt(AVE) > korelasi dengan konstruk lain
# --- 4.7 Modification indices (jika fit kurang baik) -------------------------
cat("\n--- Modification Indices (top 10) ---\n")
## 
## --- Modification Indices (top 10) ---
mi <- modificationIndices(fit_cfa, sort. = TRUE, maximum.number = 10)
print(mi[, c("lhs", "op", "rhs", "mi", "epc")])
##                lhs op   rhs     mi   epc
## 1479           JP1 ~~   JP2 52.336 0.292
## 215  JOB_RESOURCES =~ JDR20 49.612 0.605
## 1276           FW7 ~~   FW9 40.084 0.545
## 782          JDR20 ~~ JDR21 39.015 0.401
## 1420          WMI6 ~~  WMI8 34.267 0.302
## 819          JDR21 ~~ JDR22 29.805 0.265
## 1449          WMI9 ~~ WMI10 29.784 0.154
## 1484           JP3 ~~   JP4 23.374 1.170
## 252      WELLBEING =~ JDR20 21.168 0.438
## 1050         JDR28 ~~ JDR29 20.874 0.355
# Simpan hasil CFA ke Excel
addWorksheet(wb, "CFA_Factor_Loadings")
writeData(wb, "CFA_Factor_Loadings", params_cfa)
addWorksheet(wb, "CFA_CR_AVE")
writeData(wb, "CFA_CR_AVE", reliability_table)
addWorksheet(wb, "CFA_Fit_Indices")
fit_df <- as.data.frame(t(round(fit_indices_cfa, 4)))
writeData(wb, "CFA_Fit_Indices", fit_df)
# =============================================================================
# TAHAP 5: STRUCTURAL MODEL (SEM PENUH)
# =============================================================================
cat("\n========== TAHAP 5: STRUCTURAL MODEL (SEM PENUH) ==========\n")
## 
## ========== TAHAP 5: STRUCTURAL MODEL (SEM PENUH) ==========
# --- 5.1 Spesifikasi structural model ----------------------------------------
#
#  Hipotesis:
#    H1: Job Demands     (-) → Wellbeing
#    H2: Job Resources   (+) → Wellbeing
#    H3: Work Engagement (+) → Wellbeing
#    H4: Wellbeing       (-) → ITL
#    H5: Job Demands     (+) → ITL       (efek langsung)
#    H6: Wellbeing       (+) → Job Perf
#    H7: Work Engagement (+) → Job Perf
#    H8: Mediasi Wellbeing: JOB_DEMANDS → WELLBEING → ITL
#
model_sem <- '
  # ---- MEASUREMENT MODEL ----
  JOB_DEMANDS   =~ JDR11 + JDR12 + JDR13 + JDR14 + JDR15 +
                   JDR16 + JDR17 + JDR18 + JDR19 + JDR20
  JOB_RESOURCES =~ JDR21 + JDR22 + JDR23 + JDR24 + JDR25 +
                   JDR26 + JDR27 + JDR28 + JDR29 + JDR30
  WELLBEING     =~ FW1 + FW2 + FW3 + FW4 + FW5 +
                   FW6 + FW7 + FW8 + FW9 + FW10
  WORK_ENGAGE   =~ WMI1 + WMI2 + WMI3 + WMI4 + WMI5 +
                   WMI6 + WMI7 + WMI8 + WMI9 + WMI10
  ITL           =~ ITL1 + ITL2 + ITL3
  JOB_PERF      =~ JP1 + JP2 + JP3 + JP4

  # ---- STRUCTURAL PATHS (Hipotesis) ----
  # H1 & H2 & H3: Prediktor Wellbeing
  WELLBEING ~ a1*JOB_DEMANDS + a2*JOB_RESOURCES + a3*WORK_ENGAGE

  # H4 & H5: Prediktor ITL
  ITL ~ b1*WELLBEING + b2*JOB_DEMANDS

  # H6 & H7: Prediktor Job Performance
  JOB_PERF ~ c1*WELLBEING + c2*WORK_ENGAGE

  # ---- EFEK TIDAK LANGSUNG (Mediasi) ----
  # Indirect effect: JOB_DEMANDS → WELLBEING → ITL
  ind_dem_itl  := a1 * b1
  # Indirect effect: JOB_RESOURCES → WELLBEING → ITL
  ind_res_itl  := a2 * b1
  # Total effect: JOB_DEMANDS → ITL
  tot_dem_itl  := a1 * b1 + b2
'

# --- 5.2 Estimasi SEM --------------------------------------------------------
cat("Mengestimasi model SEM...\n")
## Mengestimasi model SEM...
fit_sem <- sem(
  model     = model_sem,
  data      = df_clean,
  estimator = "MLR",      # Robust untuk non-normalitas
  std.lv    = FALSE,
  missing   = "fiml"
  # Tidak pakai se="bootstrap" di sini
)

cat("Estimasi MLR selesai.\n")
## Estimasi MLR selesai.
sem_converged <- lavInspect(fit_sem, "converged")
cat("SEM converged:", sem_converged, "\n")
## SEM converged: TRUE
if (!sem_converged) {
  # --- DIAGNOSIS otomatis ---
  cat("\n========== DIAGNOSIS KONVERGENSI ==========\n")
  cat("N observasi:", nrow(df_clean), "\n")
  n_par <- tryCatch(
    lavInspect(sem(model_sem, data=df_clean, estimator="ML", do.fit=FALSE), "npar"),
    error = function(e) NA
  )
  cat("N parameter model:", n_par, "\n")
  if (!is.na(n_par)) cat("Rasio N/par:", round(nrow(df_clean)/n_par, 1),
                         "(disarankan >= 5)\n")

  var_check <- sapply(df_clean[, all_items], var, na.rm=TRUE)
  low_var <- var_check[var_check < 0.1]
  if (length(low_var) > 0) {
    cat("\nItem dengan varians sangat rendah (< 0.1) — kemungkinan penyebab:\n")
    print(round(low_var, 4))
  } else {
    cat("\nSemua item memiliki varians memadai (>= 0.1)\n")
  }

  cor_items <- cor(df_clean[, all_items], use="pairwise.complete.obs")
  diag(cor_items) <- NA
  high_cor_idx <- which(abs(cor_items) > 0.95, arr.ind=TRUE)
  if (nrow(high_cor_idx) > 0) {
    cat("\nPasangan item dengan korelasi > 0.95 (multikolinearitas):\n")
    print(high_cor_idx)
  } else {
    cat("Tidak ada multikolinearitas ekstrem (korelasi < 0.95)\n")
  }
  cat("===========================================\n\n")

  # Coba ML biasa
  warning("Model SEM tidak konvergen dengan MLR. Mencoba estimator ML...")
  fit_sem <- sem(
    model     = model_sem,
    data      = df_clean,
    estimator = "ML",
    std.lv    = FALSE,
    missing   = "fiml",
    control   = list(iter.max = 10000)
  )
  sem_converged <- lavInspect(fit_sem, "converged")
  cat("SEM converged (ML):", sem_converged, "\n")

  if (!sem_converged) {
    warning("Model SEM tetap tidak konvergen. Output mungkin tidak reliable, tapi analisis dilanjutkan.")
  }
}

# --- 5.3 Evaluasi fit SEM ----------------------------------------------------
cat("\n--- Indeks Fit SEM ---\n")
## 
## --- Indeks Fit SEM ---
fit_indices_sem <- tryCatch(
  fitMeasures(fit_sem, c(
    "chisq.scaled", "df.scaled", "pvalue.scaled",
    "cfi.scaled", "tli.scaled",
    "rmsea.scaled", "rmsea.ci.lower.scaled", "rmsea.ci.upper.scaled",
    "srmr", "aic", "bic"
  )),
  error = function(e) {
    cat("⚠ fitMeasures tidak tersedia (model belum konvergen):", conditionMessage(e), "\n")
    NULL
  }
)
if (!is.null(fit_indices_sem)) print(round(fit_indices_sem, 4))
##          chisq.scaled             df.scaled         pvalue.scaled 
##              1841.853              1023.000                 0.000 
##            cfi.scaled            tli.scaled          rmsea.scaled 
##                 0.660                 0.641                 0.063 
## rmsea.ci.lower.scaled rmsea.ci.upper.scaled                  srmr 
##                 0.059                 0.068                 0.084 
##                   aic                   bic 
##             25512.275             26013.619
# Evaluasi
cat("\n--- Evaluasi Fit Model SEM ---\n")
## 
## --- Evaluasi Fit Model SEM ---
if (!is.null(fit_indices_sem)) {
for (idx in c("cfi.scaled", "tli.scaled", "rmsea.scaled", "srmr")) {
  val <- fit_indices_sem[idx]
  label <- switch(idx,
    "cfi.scaled"  = paste("CFI:", round(val, 3),
                    ifelse(val >= 0.95, "[EXCELLENT]",
                    ifelse(val >= 0.90, "[ACCEPTABLE]", "[POOR]"))),
    "tli.scaled"  = paste("TLI:", round(val, 3),
                    ifelse(val >= 0.95, "[EXCELLENT]",
                    ifelse(val >= 0.90, "[ACCEPTABLE]", "[POOR]"))),
    "rmsea.scaled"= paste("RMSEA:", round(val, 3),
                    ifelse(val <= 0.06, "[EXCELLENT]",
                    ifelse(val <= 0.08, "[ACCEPTABLE]", "[POOR]"))),
    "srmr"        = paste("SRMR:", round(val, 3),
                    ifelse(val <= 0.08, "[ACCEPTABLE]", "[POOR]"))
  )
  cat(label, "\n")
}
} else {
  cat("⚠ Evaluasi fit dilewati karena model tidak konvergen.\n")
}
## CFI: 0.66 [POOR] 
## TLI: 0.641 [POOR] 
## RMSEA: 0.063 [ACCEPTABLE] 
## SRMR: 0.084 [POOR]
# --- 5.4 Path coefficients (standardized) ------------------------------------
cat("\n--- Path Coefficients Struktural (Standardized) ---\n")
## 
## --- Path Coefficients Struktural (Standardized) ---
cat("\n--- Path Coefficients Struktural (Standardized, MLR) ---\n")
## 
## --- Path Coefficients Struktural (Standardized, MLR) ---
params_sem <- parameterEstimates(
  fit_sem,
  standardized = TRUE
) %>%
  filter(op == "~" | op == ":=") %>%
  select(
    Dari     = lhs,
    Arah     = op,
    Ke       = rhs,
    Beta_Std = std.all,
    SE       = se,
    z        = z,
    pvalue   = pvalue,
    CI_Low   = ci.lower,
    CI_High  = ci.upper
  ) %>%
  mutate(
    across(where(is.numeric), ~ round(.x, 3)),
    Signifikan = case_when(
      pvalue < 0.001 ~ "*** p<.001",
      pvalue < 0.01  ~ "**  p<.01",
      pvalue < 0.05  ~ "*   p<.05",
      TRUE           ~ "ns"
    )
  )
print(params_sem)
##           Dari Arah            Ke Beta_Std    SE      z pvalue  CI_Low CI_High
## 1    WELLBEING    ~   JOB_DEMANDS   -0.278 0.212 -1.764  0.078  -0.791   0.042
## 2    WELLBEING    ~ JOB_RESOURCES    0.530 0.147  3.844  0.000   0.276   0.851
## 3    WELLBEING    ~   WORK_ENGAGE   -0.042 5.467 -0.261  0.794 -12.141   9.291
## 4          ITL    ~     WELLBEING   -0.149 0.176 -1.186  0.236  -0.552   0.136
## 5          ITL    ~   JOB_DEMANDS    0.051 0.237  0.407  0.684  -0.368   0.561
## 6     JOB_PERF    ~     WELLBEING    0.091 0.032  0.906  0.365  -0.033   0.090
## 7     JOB_PERF    ~   WORK_ENGAGE   -0.073 2.440 -0.315  0.753  -5.549   4.014
## 8  ind_dem_itl   :=         a1*b1    0.042 0.085  0.921  0.357  -0.088   0.244
## 9  ind_res_itl   :=         a2*b1   -0.079 0.093 -1.265  0.206  -0.299   0.064
## 10 tot_dem_itl   :=      a1*b1+b2    0.093 0.190  0.920  0.358  -0.197   0.546
##    Signifikan
## 1          ns
## 2  *** p<.001
## 3          ns
## 4          ns
## 5          ns
## 6          ns
## 7          ns
## 8          ns
## 9          ns
## 10         ns
# --- 5.5 R-squared per konstruk endogen -------------------------------------
cat("\n--- R-squared (Varians yang Dijelaskan) ---\n")
## 
## --- R-squared (Varians yang Dijelaskan) ---
r2 <- tryCatch(lavInspect(fit_sem, what = "r2"), error = function(e) NULL)
if (!is.null(r2)) {
  r2_df <- data.frame(
    Konstruk  = names(r2),
    R_squared = round(r2, 3),
    Pct       = paste0(round(r2 * 100, 1), "%")
  )
  print(r2_df)
} else {
  cat("⚠ R-squared tidak tersedia karena model tidak konvergen.\n")
  r2_df <- data.frame(Konstruk = character(), R_squared = numeric(), Pct = character())
}
##            Konstruk R_squared   Pct
## JDR11         JDR11     0.257 25.7%
## JDR12         JDR12     0.004  0.4%
## JDR13         JDR13     0.229 22.9%
## JDR14         JDR14     0.116 11.6%
## JDR15         JDR15     0.007  0.7%
## JDR16         JDR16     0.189 18.9%
## JDR17         JDR17     0.195 19.5%
## JDR18         JDR18     0.410   41%
## JDR19         JDR19     0.125 12.5%
## JDR20         JDR20     0.027  2.7%
## JDR21         JDR21     0.383 38.3%
## JDR22         JDR22     0.604 60.4%
## JDR23         JDR23     0.137 13.7%
## JDR24         JDR24     0.342 34.2%
## JDR25         JDR25     0.410   41%
## JDR26         JDR26     0.000    0%
## JDR27         JDR27     0.407 40.7%
## JDR28         JDR28     0.145 14.5%
## JDR29         JDR29     0.047  4.7%
## JDR30         JDR30     0.201 20.1%
## FW1             FW1     0.424 42.4%
## FW2             FW2     0.125 12.5%
## FW3             FW3     0.091  9.1%
## FW4             FW4     0.299 29.9%
## FW5             FW5     0.605 60.5%
## FW6             FW6     0.341 34.1%
## FW7             FW7     0.067  6.7%
## FW8             FW8     0.186 18.6%
## FW9             FW9     0.101 10.1%
## FW10           FW10     0.005  0.5%
## WMI1           WMI1     0.001  0.1%
## WMI2           WMI2     0.148 14.8%
## WMI3           WMI3     0.007  0.7%
## WMI4           WMI4     0.491 49.1%
## WMI5           WMI5     0.614 61.4%
## WMI6           WMI6     0.059  5.9%
## WMI7           WMI7     0.720   72%
## WMI8           WMI8     0.050    5%
## WMI9           WMI9     0.583 58.3%
## WMI10         WMI10     0.430   43%
## ITL1           ITL1     0.508 50.8%
## ITL2           ITL2     0.907 90.7%
## ITL3           ITL3     0.629 62.9%
## JP1             JP1     0.073  7.3%
## JP2             JP2     0.114 11.4%
## JP3             JP3     0.656 65.6%
## JP4             JP4     0.778 77.8%
## WELLBEING WELLBEING     0.404 40.4%
## ITL             ITL     0.030    3%
## JOB_PERF   JOB_PERF     0.015  1.5%
# --- 5.6 Efek mediasi (indirect effects) ------------------------------------
cat("\n--- Uji Mediasi (Bootstrap CI) ---\n")
## 
## --- Uji Mediasi (Bootstrap CI) ---
# Estimasi ulang dengan bootstrap untuk CI mediasi
cat("Mengestimasi bootstrap SEM (1000 iterasi, harap tunggu)...\n")
## Mengestimasi bootstrap SEM (1000 iterasi, harap tunggu)...
set.seed(123)
fit_sem_boot <- tryCatch(
  sem(
    model     = model_sem,
    data      = df_clean,
    estimator = "MLR",
    std.lv    = FALSE,
    missing   = "fiml",
    se        = "bootstrap",
    bootstrap = 1000
  ),
  error = function(e) { cat("Bootstrap gagal:", conditionMessage(e), "\n"); NULL }
)
## Bootstrap gagal: lavaan->lav_options_est_ml():  
##    use ML estimator for bootstrap
if (is.null(fit_sem_boot) || !lavInspect(fit_sem_boot, "converged")) {
  cat("⚠ Bootstrap SEM tidak konvergen, menggunakan hasil MLR biasa untuk mediasi.\n")
  fit_sem_boot <- fit_sem
}
## ⚠ Bootstrap SEM tidak konvergen, menggunakan hasil MLR biasa untuk mediasi.
indirect_boot <- parameterEstimates(
  fit_sem_boot,
  standardized  = TRUE,
  boot.ci.type  = "bca.simple"    # Bias-corrected accelerated
) %>%
  filter(op == ":=") %>%
  select(
    Efek     = lhs,
    Beta_Std = std.all,
    SE       = se,
    CI_Low   = ci.lower,
    CI_High  = ci.upper,
    pvalue   = pvalue
  ) %>%
  mutate(
    across(where(is.numeric), ~ round(.x, 3)),
    Signifikan    = ifelse(pvalue < 0.05, "Ya*", "Tidak"),
    CI_Tidak_Nol  = ifelse(CI_Low > 0 | CI_High < 0, "✓ Mediasi", "✗ Tidak"),
    Interpretasi  = case_when(
      CI_Low > 0 | CI_High < 0 ~ "Mediasi signifikan",
      TRUE                      ~ "Mediasi tidak signifikan"
    )
  )

print(indirect_boot)
##          Efek Beta_Std    SE CI_Low CI_High pvalue Signifikan CI_Tidak_Nol
## 1 ind_dem_itl    0.042 0.085 -0.088   0.244  0.357      Tidak      ✗ Tidak
## 2 ind_res_itl   -0.079 0.093 -0.299   0.064  0.206      Tidak      ✗ Tidak
## 3 tot_dem_itl    0.093 0.190 -0.197   0.546  0.358      Tidak      ✗ Tidak
##               Interpretasi
## 1 Mediasi tidak signifikan
## 2 Mediasi tidak signifikan
## 3 Mediasi tidak signifikan
cat("\nKeterangan: Mediasi signifikan jika 95% CI tidak mencakup angka 0\n")
## 
## Keterangan: Mediasi signifikan jika 95% CI tidak mencakup angka 0
# --- 5.7 Perbandingan model (opsional: model alternatif) --------------------
# Model alternatif: tanpa jalur langsung JOB_DEMANDS → ITL
model_alt <- '
  JOB_DEMANDS   =~ JDR11+JDR12+JDR13+JDR14+JDR15+JDR16+JDR17+JDR18+JDR19+JDR20
  JOB_RESOURCES =~ JDR21+JDR22+JDR23+JDR24+JDR25+JDR26+JDR27+JDR28+JDR29+JDR30
  WELLBEING     =~ FW1+FW2+FW3+FW4+FW5+FW6+FW7+FW8+FW9+FW10
  WORK_ENGAGE   =~ WMI1+WMI2+WMI3+WMI4+WMI5+WMI6+WMI7+WMI8+WMI9+WMI10
  ITL           =~ ITL1+ITL2+ITL3
  JOB_PERF      =~ JP1+JP2+JP3+JP4
  WELLBEING ~ JOB_DEMANDS + JOB_RESOURCES + WORK_ENGAGE
  ITL      ~ WELLBEING           # tanpa jalur langsung dari JOB_DEMANDS
  JOB_PERF ~ WELLBEING + WORK_ENGAGE
'
fit_alt <- sem(
  model_alt,
  data      = df_clean,
  estimator = "MLR",
  missing   = "fiml"
)
## Warning: lavaan->lav_lavaan_step11_estoptim():  
##    the optimizer warns that a solution has NOT been found!
cat("\n--- Perbandingan Model Utama vs Model Alternatif ---\n")
## 
## --- Perbandingan Model Utama vs Model Alternatif ---
tryCatch({
  comp <- compareFit(fit_sem, fit_alt)
  summary(comp)
}, error = function(e) {
  cat("⚠ Perbandingan model gagal:", conditionMessage(e), "\n")
  cat("Tip: Pastikan kedua model konvergen sebelum membandingkan.\n")
})
## The following models did not converge, so they are ignored:
## fit_alt
## ################### Nested Model Comparison #########################
## Chi-Squared Test Statistic (scaled)
##   Yuan-Bentler correction (Mplus variant)
## 
##             Df   AIC   BIC  Chisq Chisq diff Df diff Pr(>Chisq)    
## Saturated    0                0.0                                  
## Model     1023 25512 26014 1841.9     1841.8    1023  < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## ####################### Model Fit Indices ###########################
## ⚠ Perbandingan model gagal: invalid 'row.names' length 
## Tip: Pastikan kedua model konvergen sebelum membandingkan.
# Simpan hasil SEM ke Excel
addWorksheet(wb, "SEM_Path_Coefficients")
writeData(wb, "SEM_Path_Coefficients", params_sem)

addWorksheet(wb, "SEM_Indirect_Bootstrap")
writeData(wb, "SEM_Indirect_Bootstrap", indirect_boot)
# =============================================================================
# TAHAP 6: INTERPRETASI & PELAPORAN + VISUALISASI LENGKAP
# =============================================================================
cat("\n========== TAHAP 6: INTERPRETASI & PELAPORAN ==========\n")
## 
## ========== TAHAP 6: INTERPRETASI & PELAPORAN ==========
# Helper aman untuk fitMeasures
safe_fit <- function(fit_obj, measure) {
  tryCatch(round(fitMeasures(fit_obj, measure), 3),
           error = function(e) NA_real_)
}

# --- 6.1 Path diagram SEM (inline + file) ------------------------------------
cat("Membuat path diagram...\n")
## Membuat path diagram...
semPaths(
  object         = fit_sem,
  what           = "std",
  whatLabels     = "std",
  layout         = "tree2",
  rotation       = 2,
  edge.label.cex = 0.65,
  node.label.cex = 0.75,
  edge.color     = "black",
  style          = "ram",
  residuals      = TRUE,
  intercepts     = FALSE,
  nCharNodes     = 10,
  title          = TRUE,
  title.cex      = 1.1,
  mar            = c(2, 2, 2, 2),
  color          = list(lat = "#CECBF6", man = "#9FE1CB")
)
## Warning in qgraph::qgraph(Edgelist, labels = nLab, bidirectional = Bidir, : The
## following arguments are not documented and likely not arguments of qgraph and
## thus ignored: node.label.cex

png("plot_path_diagram_SEM.png", width = 1400, height = 1000, res = 140)
semPaths(fit_sem, what="std", whatLabels="std", layout="tree2", rotation=2,
         edge.label.cex=0.65, node.label.cex=0.75, edge.color="black",
         style="ram", residuals=TRUE, intercepts=FALSE, nCharNodes=10,
         color=list(lat="#CECBF6", man="#9FE1CB"), mar=c(2,2,2,2))
## Warning in qgraph::qgraph(Edgelist, labels = nLab, bidirectional = Bidir, : The
## following arguments are not documented and likely not arguments of qgraph and
## thus ignored: node.label.cex
dev.off()
## png 
##   2
cat("Path diagram disimpan: plot_path_diagram_SEM.png\n")
## Path diagram disimpan: plot_path_diagram_SEM.png
# --- 6.2 Plot Factor Loadings per konstruk -----------------------------------
cat("\nMembuat plot factor loadings...\n")
## 
## Membuat plot factor loadings...
p_loadings <- ggplot(params_cfa,
  aes(x = reorder(Indikator, Loading_Std), y = Loading_Std, fill = Konstruk)) +
  geom_col(width = 0.7, color = "white", linewidth = 0.3) +
  geom_hline(yintercept = 0.50, linetype = "dashed", color = "red", linewidth = 0.5) +
  geom_text(aes(label = sprintf("%.2f", Loading_Std),
                y = Loading_Std + 0.03), size = 2.8, hjust = 0) +
  coord_flip() +
  facet_wrap(~Konstruk, scales = "free_y", ncol = 2) +
  scale_fill_brewer(palette = "Set2") +
  scale_y_continuous(limits = c(0, 1.1)) +
  labs(
    title    = "Factor Loadings (Standardized) per Konstruk",
    subtitle = "Garis merah putus-putus = batas minimal 0.50",
    x        = NULL,
    y        = "Standardized Loading (λ)",
    fill     = "Konstruk"
  ) +
  theme_minimal(base_size = 10) +
  theme(
    strip.text       = element_text(face = "bold", size = 10),
    plot.title       = element_text(face = "bold", size = 13),
    legend.position  = "none",
    panel.grid.minor = element_blank()
  )

print(p_loadings)
## Warning: Removed 13 rows containing missing values or values outside the scale range
## (`geom_col()`).
## Warning: Removed 13 rows containing missing values or values outside the scale range
## (`geom_text()`).

ggsave("plot_factor_loadings.png", p_loadings, width = 12, height = 9, dpi = 150)
## Warning: Removed 13 rows containing missing values or values outside the scale range
## (`geom_col()`).
## Removed 13 rows containing missing values or values outside the scale range
## (`geom_text()`).
cat("Plot factor loadings disimpan: plot_factor_loadings.png\n")
## Plot factor loadings disimpan: plot_factor_loadings.png
# --- 6.3 Plot CR & AVE per konstruk ------------------------------------------
cat("\nMembuat plot CR dan AVE...\n")
## 
## Membuat plot CR dan AVE...
cr_ave_long <- reliability_table %>%
  select(Konstruk, CR, AVE) %>%
  tidyr::pivot_longer(cols = c(CR, AVE), names_to = "Metrik", values_to = "Nilai")

cr_ave_long$Threshold <- ifelse(cr_ave_long$Metrik == "CR", 0.70, 0.50)
cr_ave_long$Status    <- ifelse(cr_ave_long$Nilai >= cr_ave_long$Threshold, "Memenuhi", "Tidak")

p_crave <- ggplot(cr_ave_long,
  aes(x = Konstruk, y = Nilai, fill = Status)) +
  geom_col(width = 0.6, position = position_dodge(0.7), color = "white") +
  geom_hline(data = data.frame(Metrik = c("CR","AVE"), Threshold = c(0.70, 0.50)),
             aes(yintercept = Threshold), linetype = "dashed", color = "red",
             linewidth = 0.6) +
  geom_text(aes(label = sprintf("%.3f", Nilai), y = Nilai + 0.02),
            position = position_dodge(0.7), size = 3) +
  facet_wrap(~Metrik, ncol = 2) +
  scale_fill_manual(values = c("Memenuhi" = "#1D9E75", "Tidak" = "#D85A30")) +
  scale_y_continuous(limits = c(0, 1.1)) +
  labs(
    title    = "Composite Reliability (CR) & Average Variance Extracted (AVE)",
    subtitle = "CR ≥ 0.70 dan AVE ≥ 0.50 menunjukkan validitas & reliabilitas baik",
    x        = NULL, y        = "Nilai",
    fill     = "Status"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title      = element_text(face = "bold", size = 13),
    strip.text      = element_text(face = "bold"),
    axis.text.x     = element_text(angle = 30, hjust = 1),
    panel.grid.minor = element_blank()
  )

print(p_crave)

ggsave("plot_cr_ave.png", p_crave, width = 10, height = 5, dpi = 150)
cat("Plot CR & AVE disimpan: plot_cr_ave.png\n")
## Plot CR & AVE disimpan: plot_cr_ave.png
# --- 6.4 Plot path coefficients (struktural) ---------------------------------
cat("\nMembuat plot koefisien jalur...\n")
## 
## Membuat plot koefisien jalur...
path_plot_data <- params_sem %>%
  filter(Arah == "~") %>%
  mutate(
    Jalur   = paste(Dari, "→", Ke),
    Warna   = ifelse(Beta_Std > 0, "Positif", "Negatif"),
    Sig_Sym = case_when(
      pvalue < 0.001 ~ "***",
      pvalue < 0.01  ~ "**",
      pvalue < 0.05  ~ "*",
      TRUE           ~ "ns"
    )
  )

p_paths <- ggplot(path_plot_data,
  aes(x = reorder(Jalur, Beta_Std), y = Beta_Std, fill = Warna)) +
  geom_col(width = 0.6, color = "white", linewidth = 0.3) +
  geom_errorbar(aes(ymin = CI_Low, ymax = CI_High),
                width = 0.25, linewidth = 0.6) +
  geom_text(aes(label = Sig_Sym,
                y = ifelse(Beta_Std >= 0, CI_High + 0.04, CI_Low - 0.04)),
            size = 4, fontface = "bold") +
  geom_hline(yintercept = 0, linewidth = 0.5, color = "gray40") +
  coord_flip() +
  scale_fill_manual(values = c("Positif" = "#1D9E75", "Negatif" = "#D85A30")) +
  labs(
    title    = "Structural Path Coefficients (Standardized β)",
    subtitle = "Error bars = 95% CI  |  *** p<.001  ** p<.01  * p<.05  ns = tidak signifikan",
    x        = NULL,
    y        = "Standardized β",
    fill     = "Arah Pengaruh"
  ) +
  theme_minimal(base_size = 11) +
  theme(
    plot.title       = element_text(face = "bold", size = 13),
    legend.position  = "bottom",
    panel.grid.minor = element_blank()
  )

print(p_paths)

ggsave("plot_path_coefficients.png", p_paths, width = 9, height = 6, dpi = 150)
cat("Plot path coefficients disimpan: plot_path_coefficients.png\n")
## Plot path coefficients disimpan: plot_path_coefficients.png
# --- 6.5 Plot R-squared per konstruk endogen ---------------------------------
if (nrow(r2_df) > 0) {
  cat("\nMembuat plot R-squared...\n")

  p_r2 <- ggplot(r2_df, aes(x = reorder(Konstruk, R_squared), y = R_squared)) +
    geom_col(fill = "#5B85C8", width = 0.6, color = "white") +
    geom_text(aes(label = Pct, y = R_squared + 0.02),
              size = 4, fontface = "bold") +
    geom_hline(yintercept = 0.10, linetype = "dashed",
               color = "#D85A30", linewidth = 0.5) +
    coord_flip() +
    scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
    labs(
      title    = "R-squared: Varians Konstruk Endogen yang Dijelaskan",
      subtitle = "Garis merah = ambang batas minimal 10%",
      x        = NULL,
      y        = "R²"
    ) +
    theme_minimal(base_size = 11) +
    theme(
      plot.title       = element_text(face = "bold", size = 13),
      panel.grid.minor = element_blank()
    )

  print(p_r2)
  ggsave("plot_r_squared.png", p_r2, width = 8, height = 5, dpi = 150)
  cat("Plot R-squared disimpan: plot_r_squared.png\n")
}
## 
## Membuat plot R-squared...

## Plot R-squared disimpan: plot_r_squared.png
# --- 6.6 Plot perbandingan fit indices CFA vs SEM ----------------------------
cat("\nMembuat plot perbandingan fit indices...\n")
## 
## Membuat plot perbandingan fit indices...
fit_comparison <- data.frame(
  Model = c("CFA (Measurement)", "SEM (Structural)"),
  CFI   = c(safe_fit(fit_cfa, "cfi.scaled"),   safe_fit(fit_sem, "cfi.scaled")),
  TLI   = c(safe_fit(fit_cfa, "tli.scaled"),   safe_fit(fit_sem, "tli.scaled")),
  RMSEA = c(safe_fit(fit_cfa, "rmsea.scaled"), safe_fit(fit_sem, "rmsea.scaled")),
  SRMR  = c(safe_fit(fit_cfa, "srmr"),         safe_fit(fit_sem, "srmr"))
)
print(fit_comparison)
##               Model   CFI   TLI RMSEA  SRMR
## 1 CFA (Measurement) 0.661 0.640 0.063 0.083
## 2  SEM (Structural) 0.660 0.641 0.063 0.084
fit_long <- fit_comparison %>%
  tidyr::pivot_longer(cols = c(CFI, TLI, RMSEA, SRMR),
                      names_to = "Indeks", values_to = "Nilai") %>%
  mutate(
    Threshold = case_when(
      Indeks %in% c("CFI","TLI") ~ 0.90,
      Indeks == "RMSEA"           ~ 0.08,
      Indeks == "SRMR"            ~ 0.08
    ),
    Status = case_when(
      Indeks %in% c("CFI","TLI") & Nilai >= 0.95 ~ "Excellent",
      Indeks %in% c("CFI","TLI") & Nilai >= 0.90 ~ "Acceptable",
      Indeks %in% c("RMSEA","SRMR") & Nilai <= 0.06 ~ "Excellent",
      Indeks %in% c("RMSEA","SRMR") & Nilai <= 0.08 ~ "Acceptable",
      TRUE ~ "Poor"
    )
  )

p_fit <- ggplot(fit_long,
  aes(x = Model, y = Nilai, fill = Status, group = Model)) +
  geom_col(width = 0.5, color = "white") +
  geom_hline(aes(yintercept = Threshold),
             linetype = "dashed", color = "red", linewidth = 0.5) +
  geom_text(aes(label = sprintf("%.3f", Nilai), y = Nilai + 0.01),
            size = 3.5, fontface = "bold") +
  facet_wrap(~Indeks, scales = "free_y", nrow = 1) +
  scale_fill_manual(values = c("Excellent"  = "#1D9E75",
                               "Acceptable" = "#F0A500",
                               "Poor"       = "#D85A30")) +
  labs(
    title    = "Perbandingan Fit Indices: CFA vs SEM",
    subtitle = "Garis merah = ambang batas | CFI/TLI ≥ 0.90 | RMSEA/SRMR ≤ 0.08",
    x        = NULL, y = "Nilai Indeks",
    fill     = "Kategori"
  ) +
  theme_minimal(base_size = 10) +
  theme(
    plot.title       = element_text(face = "bold", size = 13),
    strip.text       = element_text(face = "bold"),
    axis.text.x      = element_text(angle = 20, hjust = 1),
    panel.grid.minor = element_blank()
  )

print(p_fit)

ggsave("plot_fit_comparison.png", p_fit, width = 10, height = 5, dpi = 150)
cat("Plot fit comparison disimpan: plot_fit_comparison.png\n")
## Plot fit comparison disimpan: plot_fit_comparison.png
# --- 6.7 Plot efek mediasi ---------------------------------------------------
if (nrow(indirect_boot) > 0) {
  cat("\nMembuat plot efek mediasi...\n")

  p_med <- ggplot(indirect_boot,
    aes(x = reorder(Efek, Beta_Std), y = Beta_Std,
        color = CI_Tidak_Nol)) +
    geom_point(size = 4) +
    geom_errorbar(aes(ymin = CI_Low, ymax = CI_High),
                  width = 0.15, linewidth = 1) +
    geom_hline(yintercept = 0, linetype = "dashed",
               color = "gray50", linewidth = 0.5) +
    coord_flip() +
    scale_color_manual(values = c("✓ Mediasi" = "#1D9E75",
                                  "✗ Tidak"   = "#D85A30")) +
    labs(
      title    = "Efek Mediasi (Indirect Effects)",
      subtitle = "Error bars = 95% CI | Hijau = mediasi signifikan (CI tidak mencakup 0)",
      x        = NULL,
      y        = "Standardized Indirect Effect (β)",
      color    = "Kesimpulan"
    ) +
    theme_minimal(base_size = 11) +
    theme(
      plot.title       = element_text(face = "bold", size = 13),
      legend.position  = "bottom",
      panel.grid.minor = element_blank()
    )

  print(p_med)
  ggsave("plot_mediasi.png", p_med, width = 8, height = 5, dpi = 150)
  cat("Plot mediasi disimpan: plot_mediasi.png\n")
}
## 
## Membuat plot efek mediasi...

## Plot mediasi disimpan: plot_mediasi.png
# --- 6.8 Tabel ringkasan hipotesis -------------------------------------------
cat("\n--- Ringkasan Hasil Hipotesis ---\n")
## 
## --- Ringkasan Hasil Hipotesis ---
hipotesis <- data.frame(
  Hipotesis = c("H1","H2","H3","H4","H5","H6","H7","H8 (mediasi)"),
  Jalur = c(
    "JOB_DEMANDS → WELLBEING",
    "JOB_RESOURCES → WELLBEING",
    "WORK_ENGAGE → WELLBEING",
    "WELLBEING → ITL",
    "JOB_DEMANDS → ITL (langsung)",
    "WELLBEING → JOB_PERF",
    "WORK_ENGAGE → JOB_PERF",
    "JOB_DEMANDS → WELLBEING → ITL"
  ),
  Arah_Prediksi = c("-","+","+","-","+","+","+","-")
)
print(hipotesis)
##      Hipotesis                         Jalur Arah_Prediksi
## 1           H1       JOB_DEMANDS → WELLBEING             -
## 2           H2     JOB_RESOURCES → WELLBEING             +
## 3           H3       WORK_ENGAGE → WELLBEING             +
## 4           H4               WELLBEING → ITL             -
## 5           H5  JOB_DEMANDS → ITL (langsung)             +
## 6           H6          WELLBEING → JOB_PERF             +
## 7           H7        WORK_ENGAGE → JOB_PERF             +
## 8 H8 (mediasi) JOB_DEMANDS → WELLBEING → ITL             -
# --- 6.9 Simpan semua ke Excel -----------------------------------------------
addWorksheet(wb, "Fit_Comparison")
writeData(wb, "Fit_Comparison", fit_comparison)
addWorksheet(wb, "Hipotesis_Summary")
writeData(wb, "Hipotesis_Summary", hipotesis)

if (!is.null(fit_indices_sem)) {
  addWorksheet(wb, "SEM_Fit_Indices")
  writeData(wb, "SEM_Fit_Indices",
            as.data.frame(t(round(fit_indices_sem, 4))))
}
if (nrow(r2_df) > 0) {
  addWorksheet(wb, "SEM_R_Squared")
  writeData(wb, "SEM_R_Squared", r2_df)
}

saveWorkbook(wb, "SEM_Hasil_Lengkap.xlsx", overwrite = TRUE)
cat("\nSemua hasil disimpan ke: SEM_Hasil_Lengkap.xlsx\n")
## 
## Semua hasil disimpan ke: SEM_Hasil_Lengkap.xlsx
# --- 6.10 Ringkasan akhir ----------------------------------------------------
cat("\n=============================================================\n")
## 
## =============================================================
cat("  RINGKASAN AKHIR ANALISIS SEM\n")
##   RINGKASAN AKHIR ANALISIS SEM
cat("=============================================================\n")
## =============================================================
cat("Dataset   :", nrow(df_clean), "responden,", length(all_items), "item\n")
## Dataset   : 200 responden, 47 item
cat("Konstruk  :", length(indicators), "konstruk laten\n")
## Konstruk  : 6 konstruk laten
cat("Estimator : MLR (Robust Maximum Likelihood)\n")
## Estimator : MLR (Robust Maximum Likelihood)
cat("JP1-JP4   : Dinormalisasi dari skala 1-10 ke 1-5\n")
## JP1-JP4   : Dinormalisasi dari skala 1-10 ke 1-5
cat("\nFit Indeks CFA:\n")
## 
## Fit Indeks CFA:
cat("  CFI   =", safe_fit(fit_cfa, "cfi.scaled"), "\n")
##   CFI   = 0.661
cat("  RMSEA =", safe_fit(fit_cfa, "rmsea.scaled"), "\n")
##   RMSEA = 0.063
cat("  SRMR  =", safe_fit(fit_cfa, "srmr"), "\n")
##   SRMR  = 0.083
cat("\nFit Indeks SEM:\n")
## 
## Fit Indeks SEM:
cat("  CFI   =", safe_fit(fit_sem, "cfi.scaled"), "\n")
##   CFI   = 0.66
cat("  RMSEA =", safe_fit(fit_sem, "rmsea.scaled"), "\n")
##   RMSEA = 0.063
cat("  SRMR  =", safe_fit(fit_sem, "srmr"), "\n")
##   SRMR  = 0.084
cat("\nFile output:\n")
## 
## File output:
cat("  SEM_Hasil_Lengkap.xlsx\n")
##   SEM_Hasil_Lengkap.xlsx
cat("  plot_korelasi_konstruk.png\n")
##   plot_korelasi_konstruk.png
cat("  plot_path_diagram_SEM.png\n")
##   plot_path_diagram_SEM.png
cat("  plot_factor_loadings.png\n")
##   plot_factor_loadings.png
cat("  plot_cr_ave.png\n")
##   plot_cr_ave.png
cat("  plot_path_coefficients.png\n")
##   plot_path_coefficients.png
cat("  plot_r_squared.png\n")
##   plot_r_squared.png
cat("  plot_fit_comparison.png\n")
##   plot_fit_comparison.png
cat("  plot_mediasi.png\n")
##   plot_mediasi.png
cat("=============================================================\n")
## =============================================================