Nothing
# ============================================================
# ConverTier2
# Converter of Assessment Scores with Tiered Instruments
#
# Authors:
# 1. Bhilan Rhiyu Antama
# 2. Heni Setiyaningsih
# 3. Edi Istiyono
# ============================================================
# ============================================================
# LIBRARIES
# ============================================================
library(shiny)
library(DT)
library(shinythemes)
library(shinyjs)
library(openxlsx)
# ============================================================
# HELPER FUNCTION
# Generate default scoring rules
# ============================================================
generate_default_rules <- function(n_tier) {
patterns <- apply(
expand.grid(
rep(
list(c(1, 0)),
n_tier
)
),
1,
paste0,
collapse = ""
)
scores <- rev(seq_along(patterns))
paste(
paste0(
patterns,
" = ",
scores
),
collapse = "\n"
)
}
# ============================================================
# UI
# ============================================================
ui <- fluidPage(
# ----------------------------------------------------------
# ShinyJS
# ----------------------------------------------------------
useShinyjs(),
# ----------------------------------------------------------
# Theme
# ----------------------------------------------------------
theme = shinytheme("flatly"),
# ----------------------------------------------------------
# HEAD
# ----------------------------------------------------------
tags$head(
# Google Fonts
tags$link(
rel = "preconnect",
href = "https://fonts.googleapis.com"
),
tags$link(
rel = "preconnect",
href = "https://fonts.gstatic.com",
crossorigin = "anonymous"
),
tags$link(
href = paste0(
"https://fonts.googleapis.com/css2?",
"family=JetBrains+Mono:wght@400;500;600&",
"family=Plus+Jakarta+Sans:wght@400;500;600;700;800&",
"display=swap"
),
rel = "stylesheet"
),
# ========================================================
# CSS
# ========================================================
tags$style(HTML("
/* =====================================================
GLOBAL
===================================================== */
body {
background: #f4f7fb;
font-family:
'Plus Jakarta Sans',
sans-serif;
color: #243447;
}
.container-fluid {
max-width: 1500px;
padding:
0 28px 40px 28px;
}
/* =====================================================
HEADER
===================================================== */
.app-header {
background:
linear-gradient(
135deg,
#1f4e79 0%,
#2878b5 45%,
#3ca6c9 100%
);
color: white;
padding:
32px 36px;
margin:
0 -28px 28px -28px;
border-radius:
0 0 22px 22px;
box-shadow:
0 8px 24px
rgba(31, 78, 121, 0.20);
}
.app-title {
font-size: 30px;
font-weight: 800;
margin:
0 0 8px 0;
letter-spacing:
-0.5px;
}
.app-subtitle {
font-size: 14px;
opacity: 0.92;
margin: 0;
font-weight: 400;
}
/* =====================================================
STEP CARDS
===================================================== */
.step-card {
background: white;
border-radius: 16px;
padding:
22px 24px;
margin-bottom: 18px;
border:
1px solid #e6edf4;
box-shadow:
0 4px 16px
rgba(31, 78, 121, 0.06);
}
.step-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 18px;
}
.step-number {
width: 34px;
height: 34px;
min-width: 34px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
background: #eaf4fb;
color: #1f6f9f;
font-size: 14px;
font-weight: 800;
}
.step-title {
font-size: 17px;
font-weight: 700;
color: #253b53;
margin: 0;
}
.step-description {
color: #718096;
font-size: 13px;
margin:
4px 0 0 0;
}
/* =====================================================
FORM
===================================================== */
.form-group {
margin-bottom: 14px;
}
label {
font-size: 13px;
font-weight: 600;
color: #34495e;
}
.form-control {
border:
1px solid #dce5ee;
border-radius: 9px;
box-shadow: none;
font-size: 13px;
}
.form-control:focus {
border-color: #4ba3c7;
box-shadow:
0 0 0 3px
rgba(75, 163, 199, 0.10);
}
textarea.form-control {
font-family:
'JetBrains Mono',
monospace;
font-size: 12px;
line-height: 1.7;
min-height: 190px;
}
/* =====================================================
BUTTONS
===================================================== */
.btn {
border-radius:
9px !important;
font-weight:
600 !important;
font-size:
13px !important;
padding:
9px 16px !important;
opacity:
1 !important;
text-shadow:
none !important;
}
/* -----------------------------------------------------
PRIMARY BUTTON
----------------------------------------------------- */
.btn-primary,
.btn-primary:focus,
.btn-primary:active {
background:
linear-gradient(
135deg,
#2878b5 0%,
#3195c1 100%
) !important;
background-image:
linear-gradient(
135deg,
#2878b5 0%,
#3195c1 100%
) !important;
border:
none !important;
color:
#ffffff !important;
border-radius:
9px !important;
font-weight:
700 !important;
opacity:
1 !important;
box-shadow:
0 4px 10px
rgba(40, 120, 181, 0.20)
!important;
}
.btn-primary:hover {
background:
linear-gradient(
135deg,
#1f679d 0%,
#2878b5 100%
) !important;
background-image:
linear-gradient(
135deg,
#1f679d 0%,
#2878b5 100%
) !important;
color:
#ffffff !important;
transform:
translateY(-1px);
box-shadow:
0 6px 14px
rgba(40, 120, 181, 0.25)
!important;
}
/* -----------------------------------------------------
SUCCESS / DOWNLOAD BUTTON
----------------------------------------------------- */
.btn-success,
.btn-success:focus,
.btn-success:active {
background:
linear-gradient(
135deg,
#2e9d73 0%,
#38ad82 100%
) !important;
background-image:
linear-gradient(
135deg,
#2e9d73 0%,
#38ad82 100%
) !important;
border:
none !important;
color:
#ffffff !important;
opacity:
1 !important;
box-shadow:
0 4px 10px
rgba(46, 157, 115, 0.18)
!important;
}
.btn-success:hover {
background:
linear-gradient(
135deg,
#258761 0%,
#2e9d73 100%
) !important;
background-image:
linear-gradient(
135deg,
#258761 0%,
#2e9d73 100%
) !important;
color:
#ffffff !important;
transform:
translateY(-1px);
}
/* -----------------------------------------------------
DEFAULT / BROWSE BUTTON
----------------------------------------------------- */
.btn-default,
.btn-default:focus,
.btn-default:active {
background:
#f5f7fa !important;
background-image:
none !important;
border:
1px solid #dce5ee !important;
color:
#34495e !important;
opacity:
1 !important;
box-shadow:
none !important;
}
.btn-default:hover {
background:
#eaf0f5 !important;
border-color:
#cbd8e3 !important;
color:
#253b53 !important;
}
/* -----------------------------------------------------
DISABLED BUTTON
----------------------------------------------------- */
.btn:disabled,
.btn.disabled {
opacity:
0.55 !important;
cursor:
not-allowed;
}
/* -----------------------------------------------------
FILE INPUT
----------------------------------------------------- */
.form-control[readonly] {
background:
#ffffff !important;
color:
#607285 !important;
opacity:
1 !important;
}
.shiny-input-container {
width: 100%;
}
/* =====================================================
RULE EDITOR
===================================================== */
.template-box {
background:
#f7fafc;
border:
1px dashed #c9d7e5;
border-radius:
10px;
padding:
12px 14px;
margin-bottom:
12px;
font-family:
'JetBrains Mono',
monospace;
font-size:
11px;
color:
#617386;
}
.template-title {
font-family:
'Plus Jakarta Sans',
sans-serif;
font-weight:
700;
font-size:
12px;
color:
#4a5d70;
margin-bottom:
6px;
}
/* =====================================================
STATUS
===================================================== */
.status-bar {
display:
none;
border-radius:
9px;
padding:
10px 13px;
margin-top:
12px;
font-size:
12px;
font-weight:
500;
}
.status-success {
display:
block;
background:
#eaf8f1;
color:
#237653;
border:
1px solid #c8eadb;
}
.status-error {
display:
block;
background:
#fff0f0;
color:
#b43b3b;
border:
1px solid #f2cccc;
}
.status-info {
display:
block;
background:
#edf7fc;
color:
#256a8d;
border:
1px solid #cde6f2;
}
/* =====================================================
RULE PREVIEW
===================================================== */
.rule-preview {
background:
#f8fafc;
border-radius:
12px;
padding:
14px;
max-height:
430px;
overflow-y:
auto;
}
.rule-row {
display:
flex;
align-items:
center;
padding:
8px 4px;
border-bottom:
1px solid #edf1f5;
}
.rule-row:last-child {
border-bottom:
none;
}
.rule-pattern {
flex: 1;
font-family:
'JetBrains Mono',
monospace;
font-size:
12px;
font-weight:
600;
}
.rule-score {
width:
46px;
height:
30px;
border-radius:
8px;
display:
flex;
align-items:
center;
justify-content:
center;
background:
#eaf4fb;
color:
#21658e;
font-size:
12px;
font-weight:
700;
}
.tier-badge {
display:
inline-block;
border-radius:
5px;
padding:
3px 6px;
margin-right:
3px;
background:
#edf2f7;
color:
#536576;
font-size:
10px;
font-weight:
700;
}
/* =====================================================
DATA INFORMATION
===================================================== */
.data-info {
display:
flex;
gap:
10px;
flex-wrap:
wrap;
margin-top:
12px;
}
.info-chip {
background:
#f4f7fa;
border:
1px solid #e2e8f0;
border-radius:
8px;
padding:
7px 11px;
font-size:
11px;
color:
#607285;
}
.info-chip strong {
color:
#263c51;
}
/* =====================================================
DATATABLE
===================================================== */
.dataTables_wrapper {
font-size:
12px;
}
table.dataTable thead th {
background:
#f4f7fb !important;
color:
#3d5267 !important;
border-bottom:
1px solid #dce5ee !important;
font-weight:
700 !important;
}
table.dataTable tbody td {
vertical-align:
middle;
}
/* =====================================================
FOOTER
===================================================== */
.app-footer {
text-align:
center;
color:
#8a99a8;
font-size:
11px;
margin-top:
24px;
padding-top:
16px;
border-top:
1px solid #e5ebf1;
}
/* =====================================================
RESPONSIVE
===================================================== */
@media (max-width: 991px) {
.container-fluid {
padding:
0 15px 30px 15px;
}
.app-header {
margin:
0 -15px 20px -15px;
padding:
26px 22px;
}
.app-title {
font-size:
23px;
}
.step-card {
padding:
18px;
}
}
"))
),
# ==========================================================
# HEADER
# ==========================================================
div(
class = "app-header",
h1(
class = "app-title",
"Converter of Assessment Scores with Tiered Instruments"
),
p(
class = "app-subtitle",
"Konverter skor 0/1 per jawaban & alasan sesuai pedoman penskoran Anda"
)
),
# ==========================================================
# MAIN
# ==========================================================
fluidRow(
# ========================================================
# LEFT COLUMN
# ========================================================
column(
width = 7,
# ======================================================
# STEP 1
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"1"
),
div(
h3(
class = "step-title",
"Tentukan Jumlah Tier"
),
p(
class = "step-description",
"Pilih jumlah komponen respons pada setiap butir."
)
)
),
selectInput(
inputId = "n_tier",
label = "Jumlah tier:",
choices = c(
"2 Tier" = 2,
"3 Tier" = 3,
"4 Tier" = 4
),
selected = 2,
width = "100%"
)
),
# ======================================================
# STEP 2
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"2"
),
div(
h3(
class = "step-title",
"Atur Pedoman Penskoran"
),
p(
class = "step-description",
"Tentukan skor untuk setiap kombinasi respons."
)
)
),
div(
class = "template-box",
div(
class = "template-title",
"Format aturan"
),
textOutput(
"template_text",
inline = TRUE
)
),
textAreaInput(
inputId = "rules_text",
label = "Aturan penskoran:",
value =
generate_default_rules(2),
rows = 10,
width = "100%"
),
actionButton(
inputId = "apply_rules",
label = "✔ Terapkan Aturan",
class = "btn-primary"
),
div(
id = "rules_status",
class = "status-bar"
)
),
# ======================================================
# STEP 3
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"3"
),
div(
h3(
class = "step-title",
"Unggah Data"
),
p(
class = "step-description",
"Unggah file Excel dengan respons biner 0/1."
)
)
),
fileInput(
inputId = "file",
label = "Pilih file Excel:",
accept = c(
".xlsx",
".xls"
),
width = "100%"
),
div(
class = "template-box",
div(
class = "template-title",
"Format kolom yang diharapkan"
),
HTML(
"<b>Jwb_1</b> | <b>Als_1</b> |
<b>Jwb_2</b> | <b>Als_2</b> | ..."
),
br(),
span(
"Semua nilai respons harus berupa 0 atau 1."
)
),
div(
id = "upload_status",
class = "status-bar"
),
uiOutput(
"data_info"
)
),
# ======================================================
# STEP 4
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"4"
),
div(
h3(
class = "step-title",
"Proses & Unduh"
),
p(
class = "step-description",
"Konversi respons menjadi skor berdasarkan aturan aktif."
)
)
),
actionButton(
inputId = "process",
label = "⚙ Proses Konversi",
class = "btn-primary"
),
downloadButton(
outputId = "download",
label = "⬇ Unduh Hasil Excel",
class = "btn-success"
),
div(
id = "process_status",
class = "status-bar"
)
)
),
# ========================================================
# RIGHT COLUMN
# ========================================================
column(
width = 5,
# ======================================================
# ACTIVE RULES
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"✓"
),
div(
h3(
class = "step-title",
"Pedoman Penskoran Aktif"
),
p(
class = "step-description",
"Aturan yang akan digunakan dalam proses konversi."
)
)
),
uiOutput(
"rules_preview"
)
),
# ======================================================
# RESULT PREVIEW
# ======================================================
div(
class = "step-card",
div(
class = "step-header",
div(
class = "step-number",
"✓"
),
div(
h3(
class = "step-title",
"Preview Hasil"
),
p(
class = "step-description",
"Pratinjau skor butir setelah proses konversi."
)
)
),
DTOutput(
"result_preview"
)
)
)
),
# ==========================================================
# FOOTER
# ==========================================================
div(
class = "app-footer",
"ConverTier2 • Converter of Assessment Scores with Tiered Instruments",
br(),
"Bhilan Rhiyu Antama • Heni Setiyaningsih • Edi Istiyono"
)
)
# ============================================================
# SERVER
# ============================================================
server <- function(input, output, session) {
# ==========================================================
# REACTIVE VALUES
# ==========================================================
rv <- reactiveValues(
rules = NULL,
raw_data = NULL,
scored_data = NULL
)
# ==========================================================
# STATUS HELPER
# ==========================================================
set_status <- function(
id,
type,
message
) {
shinyjs::removeClass(
id,
c(
"status-success",
"status-error",
"status-info"
)
)
shinyjs::addClass(
id,
paste0(
"status-",
type
)
)
shinyjs::html(
id,
message
)
}
# ==========================================================
# STEP 1
# TIER CHANGE
# ==========================================================
observeEvent(
input$n_tier,
{
nt <-
as.integer(
input$n_tier
)
updateTextAreaInput(
session = session,
inputId =
"rules_text",
value =
generate_default_rules(nt)
)
rv$rules <-
NULL
rv$scored_data <-
NULL
shinyjs::disable(
"download"
)
output$result_preview <-
renderDT(NULL)
output$rules_preview <-
renderUI({
div(
class =
"rule-preview",
div(
style = "
color:#8a99a8;
text-align:center;
padding:30px 10px;
font-size:12px;
",
"Belum ada aturan yang diterapkan."
)
)
})
},
ignoreInit = TRUE
)
# ==========================================================
# TEMPLATE TEXT
# ==========================================================
output$template_text <-
renderText({
nt <-
as.integer(
input$n_tier
)
paste0(
paste(
paste0(
"Tier",
seq_len(nt)
),
collapse =
" | "
),
" = skor"
)
})
# ==========================================================
# STEP 2
# APPLY RULES
# ==========================================================
observeEvent(
input$apply_rules,
{
nt <-
as.integer(
input$n_tier
)
result <-
tryCatch(
{
rules <-
ConverTier2::parse_rules(
input$rules_text
)
# ----------------------------------------------
# Check pattern length
# ----------------------------------------------
bad_length <-
names(rules)[
nchar(
names(rules)
) != nt
]
if (
length(bad_length) > 0
) {
stop(
paste0(
"Panjang pola berikut tidak sesuai ",
"dengan jumlah tier (",
nt,
"): ",
paste(
bad_length,
collapse = ", "
)
)
)
}
rv$rules <-
rules
rv$scored_data <-
NULL
shinyjs::disable(
"download"
)
list(
success = TRUE,
rules = rules
)
},
error = function(e) {
list(
success = FALSE,
message =
conditionMessage(e)
)
}
)
# ------------------------------------------------------
# SUCCESS
# ------------------------------------------------------
if (
result$success
) {
set_status(
"rules_status",
"success",
paste0(
"✓ Aturan berhasil diterapkan. ",
length(rv$rules),
" aturan aktif."
)
)
}
# ------------------------------------------------------
# ERROR
# ------------------------------------------------------
else {
rv$rules <-
NULL
set_status(
"rules_status",
"error",
paste0(
"✕ ",
result$message
)
)
}
}
)
# ==========================================================
# ACTIVE RULE PREVIEW
# ==========================================================
output$rules_preview <-
renderUI({
req(
rv$rules
)
rules <-
rv$rules
nt <-
as.integer(
input$n_tier
)
rows <-
lapply(
seq_along(rules),
function(i) {
pattern <-
names(rules)[i]
badges <-
lapply(
seq_len(nt),
function(j) {
span(
class =
"tier-badge",
paste0(
"T",
j,
": ",
substr(
pattern,
j,
j
)
)
)
}
)
div(
class =
"rule-row",
div(
class =
"rule-pattern",
badges
),
div(
class =
"rule-score",
as.character(
rules[i]
)
)
)
}
)
div(
class =
"rule-preview",
rows
)
})
# ==========================================================
# STEP 3
# UPLOAD DATA
# ==========================================================
observeEvent(
input$file,
{
rv$raw_data <-
NULL
rv$scored_data <-
NULL
shinyjs::disable(
"download"
)
if (
is.null(input$file)
) {
return()
}
result <-
tryCatch(
{
data <-
readxl::read_excel(
input$file$datapath
)
data <-
as.data.frame(
data
)
if (
nrow(data) == 0 ||
ncol(data) == 0
) {
stop(
"File tidak berisi data."
)
}
rv$raw_data <-
data
list(
success = TRUE,
data = data
)
},
error = function(e) {
list(
success = FALSE,
message =
conditionMessage(e)
)
}
)
# ------------------------------------------------------
# SUCCESS
# ------------------------------------------------------
if (
result$success
) {
set_status(
"upload_status",
"success",
paste0(
"✓ File berhasil dibaca: ",
nrow(
rv$raw_data
),
" responden × ",
ncol(
rv$raw_data
),
" kolom."
)
)
}
# ------------------------------------------------------
# ERROR
# ------------------------------------------------------
else {
rv$raw_data <-
NULL
set_status(
"upload_status",
"error",
paste0(
"✕ ",
result$message
)
)
}
}
)
# ==========================================================
# DATA INFORMATION
# ==========================================================
output$data_info <-
renderUI({
req(
rv$raw_data
)
div(
class =
"data-info",
div(
class =
"info-chip",
strong(
"Responden: "
),
nrow(
rv$raw_data
)
),
div(
class =
"info-chip",
strong(
"Kolom: "
),
ncol(
rv$raw_data
)
),
div(
class =
"info-chip",
strong(
"Tier: "
),
input$n_tier
),
div(
class =
"info-chip",
strong(
"Butir: "
),
ncol(
rv$raw_data
) /
as.integer(
input$n_tier
)
)
)
})
# ==========================================================
# STEP 4
# PROCESS
# ==========================================================
observeEvent(
input$process,
{
# ------------------------------------------------------
# RULE CHECK
# ------------------------------------------------------
if (
is.null(rv$rules)
) {
set_status(
"process_status",
"error",
"✕ Terapkan pedoman penskoran terlebih dahulu."
)
return()
}
# ------------------------------------------------------
# DATA CHECK
# ------------------------------------------------------
if (
is.null(rv$raw_data)
) {
set_status(
"process_status",
"error",
"✕ Unggah file Excel terlebih dahulu."
)
return()
}
# ------------------------------------------------------
# PROCESS
# ------------------------------------------------------
result <-
tryCatch(
{
nt <-
as.integer(
input$n_tier
)
# ----------------------------------------------
# Package validation
# ----------------------------------------------
ConverTier2::validate_data(
data =
rv$raw_data,
n_tier =
nt
)
# ----------------------------------------------
# Package conversion
# ----------------------------------------------
scored <-
ConverTier2::convert_tier(
data =
rv$raw_data,
rules =
rv$rules,
n_tier =
nt
)
rv$scored_data <-
scored
list(
success = TRUE,
data = scored
)
},
error = function(e) {
list(
success = FALSE,
message =
conditionMessage(e)
)
}
)
# ------------------------------------------------------
# SUCCESS
# ------------------------------------------------------
if (
result$success
) {
shinyjs::enable(
"download"
)
set_status(
"process_status",
"success",
paste0(
"✓ Konversi berhasil. ",
nrow(
rv$scored_data
),
" responden × ",
ncol(
rv$scored_data
),
" butir berhasil diproses."
)
)
}
# ------------------------------------------------------
# ERROR
# ------------------------------------------------------
else {
rv$scored_data <-
NULL
shinyjs::disable(
"download"
)
set_status(
"process_status",
"error",
paste0(
"✕ ",
result$message
)
)
}
}
)
# ==========================================================
# RESULT PREVIEW
# ==========================================================
output$result_preview <-
renderDT({
req(
rv$scored_data
)
datatable(
rv$scored_data,
rownames =
FALSE,
options = list(
pageLength =
8,
scrollX =
TRUE,
autoWidth =
TRUE,
lengthMenu =
c(
5,
8,
10,
25,
50
)
)
)
})
# ==========================================================
# DOWNLOAD EXCEL
# ==========================================================
output$download <-
downloadHandler(
# ------------------------------------------------------
# Filename
# ------------------------------------------------------
filename = function() {
paste0(
"hasil_skor_tier2_",
format(
Sys.time(),
"%Y%m%d_%H%M%S"
),
".xlsx"
)
},
# ------------------------------------------------------
# Content
# ------------------------------------------------------
content = function(file) {
req(
rv$scored_data
)
req(
rv$rules
)
# ====================================================
# CREATE WORKBOOK
# ====================================================
wb <-
createWorkbook()
# ====================================================
# STYLES
# ====================================================
header_style <-
createStyle(
fontName =
"Arial",
fontSize =
10,
textDecoration =
"bold",
halign =
"center",
valign =
"center",
fgFill =
"#2878B5",
fontColour =
"#FFFFFF",
border =
"TopBottomLeftRight",
borderColour =
"#D9E2EC"
)
body_style <-
createStyle(
fontName =
"Arial",
fontSize =
10,
valign =
"center"
)
# ====================================================
# SHEET 1
# SKOR BUTIR
# ====================================================
addWorksheet(
wb,
"Skor Butir"
)
writeData(
wb,
sheet =
"Skor Butir",
x =
rv$scored_data,
startRow =
1,
startCol =
1,
withFilter =
TRUE
)
# Header
addStyle(
wb,
sheet =
"Skor Butir",
style =
header_style,
rows =
1,
cols =
seq_len(
ncol(
rv$scored_data
)
),
gridExpand =
TRUE
)
# Body
if (
nrow(
rv$scored_data
) > 0
) {
addStyle(
wb,
sheet =
"Skor Butir",
style =
body_style,
rows =
2:(
nrow(
rv$scored_data
) + 1
),
cols =
seq_len(
ncol(
rv$scored_data
)
),
gridExpand =
TRUE
)
}
# Column widths
setColWidths(
wb,
sheet =
"Skor Butir",
cols =
seq_len(
ncol(
rv$scored_data
)
),
widths =
"auto"
)
# Freeze header
freezePane(
wb,
sheet =
"Skor Butir",
firstActiveRow =
2
)
# ====================================================
# SHEET 2
# INFO PENSKORAN
# ====================================================
addWorksheet(
wb,
"Info Penskoran"
)
info <-
data.frame(
Informasi = c(
"Nama Aplikasi",
"Jumlah Tier",
"Jumlah Responden",
"Jumlah Butir",
"Jumlah Aturan",
"Tanggal Pemrosesan"
),
Nilai = c(
"ConverTier2",
as.character(
input$n_tier
),
nrow(
rv$raw_data
),
ncol(
rv$scored_data
),
length(
rv$rules
),
format(
Sys.time(),
"%Y-%m-%d %H:%M:%S"
)
),
stringsAsFactors =
FALSE
)
writeData(
wb,
sheet =
"Info Penskoran",
x =
info,
startRow =
1,
startCol =
1
)
addStyle(
wb,
sheet =
"Info Penskoran",
style =
header_style,
rows =
1,
cols =
1:2,
gridExpand =
TRUE
)
addStyle(
wb,
sheet =
"Info Penskoran",
style =
body_style,
rows =
2:(
nrow(info) + 1
),
cols =
1:2,
gridExpand =
TRUE
)
# ====================================================
# SCORING RULES
# ====================================================
start_row <-
nrow(info) + 4
writeData(
wb,
sheet =
"Info Penskoran",
x =
"Pedoman Penskoran",
startRow =
start_row,
startCol =
1
)
addStyle(
wb,
sheet =
"Info Penskoran",
style =
header_style,
rows =
start_row,
cols =
1:2,
gridExpand =
TRUE
)
rules_df <-
data.frame(
Pola =
names(
rv$rules
),
Skor =
as.numeric(
rv$rules
),
stringsAsFactors =
FALSE
)
writeData(
wb,
sheet =
"Info Penskoran",
x =
rules_df,
startRow =
start_row + 1,
startCol =
1,
colNames =
TRUE
)
addStyle(
wb,
sheet =
"Info Penskoran",
style =
header_style,
rows =
start_row + 1,
cols =
1:2,
gridExpand =
TRUE
)
if (
nrow(
rules_df
) > 0
) {
addStyle(
wb,
sheet =
"Info Penskoran",
style =
body_style,
rows = (
start_row + 2
): (
start_row +
nrow(rules_df) +
1
),
cols =
1:2,
gridExpand =
TRUE
)
}
setColWidths(
wb,
sheet =
"Info Penskoran",
cols =
1:2,
widths =
c(
28,
18
)
)
freezePane(
wb,
sheet =
"Info Penskoran",
firstActiveRow =
2
)
# ====================================================
# SAVE
# ====================================================
saveWorkbook(
wb,
file =
file,
overwrite =
TRUE
)
}
)
# ==========================================================
# INITIAL STATE
# ==========================================================
shinyjs::disable(
"download"
)
}
# ============================================================
# RUN APPLICATION
# ============================================================
shinyApp(
ui = ui,
server = server
)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.