Total Help
–
topik bantuan
Aplikasi
–
sumber pemanggil API
Form / Segment
–
grup form terdaftar
Total FAQ
–
tanya-jawab
Data Help
Satu baris = satu topik (judul + isi + FAQ + dipakai-oleh). Ikon ? pratinjau · ikon chat kelola FAQ & relasi · tombol </> salin kode Flutter siap pakai.
Belum ada data help
Tambahkan help pertama, mis. reservasi / form_reservasi / nama_tamu.
Menampilkan 0 data · Public API tanpa login: GET /api/v1/help?app=&form=
Pengguna
Hanya admin (.env) yang bisa menambah user. User tambahan bisa login & kelola konten help. Admin tidak tersimpan di tabel ini.
| Username / Nama | Dibuat | Aksi |
|---|
Belum ada user tambahan
Klik Tambah User untuk memberi akses kelola konten ke staf lain.
Dokumentasi untuk developer Flutter
Semua cara ambil data & pasang widget hanya via Flutter: konsep data, endpoint public + padanannya di HelpApi, widget tombol ? + bottom sheet, setup client, dan playground coba langsung. Base URL tutorial: https://help.emerald-system.id (playground di bawah memakai server ini).
1 · Konsep data
Satu baris help = satu topik bantuan untuk satu field di aplikasi lain.
| Kolom | Artinya |
|---|---|
| app / form / group / field | Alamat unik topik. Contoh: reservasi / form_reservasi / Data Tamu / nama_tamu. Semua bebas diketik — aplikasi, form, dan group baru otomatis jadi kategori (tidak terkunci reservasi). |
| title | Judul bantuan, mis. “Cara mengisi Nama Tamu” |
| description + content | Keterangan singkat + isi lengkap. Content mendukung HTML (b, i, ul, ol, li, a, p) via toolbar editor; script otomatis dibuang |
| group | Grouping opsional dalam satu form (mis. Data Tamu, Jadwal, Kamar) agar puluhan tombol ? tampil berkelompok di widget & panel |
| scope | field = satu tombol/field · section = satu bagian/group (key section:namagroup) · global = seluruh layar (key _global, tampil sebagai banner + bisa menautkan tombol-tombolnya di langkah Review) |
| audience | Dipakai oleh siapa, mis. Kasir, Admin, Tamu |
| faq | Daftar tanya-jawab per topik (kelola via ikon chat) |
| related | Bantuan terkait lain — otomatis (satu form) + manual (pilihan sendiri) |
| image_url | Gambar icon/screenshot (upload via form, maks 2MB). Blok gambar disembunyikan otomatis bila kosong |
| created_by / updated_by | Audit: siapa membuat & terakhir mengubah + kapan |
2 · Core data — semua properti & contoh response
Inilah semua data inti yang dikembalikan API detail (?field= / /helps/:id). List ringkas mengembalikan kolom yang sama tanpa content + faqs + related, plus faq_count.
| Properti | Isi |
|---|---|
| id, app_name, form_segment, group_name, scope | Alamat & pengelompokan topik |
| field_name, field_label, field_type, is_required | Key unik + label + tipe (text/button/tab/…) + wajib |
| title, description, content | Judul + keterangan singkat + isi HTML (b/i/ul/ref/gambar) |
| audience, example_value, image_url | Dipakai-oleh + contoh benar + gambar (/uploads/…) |
| faqs[] | Tanya-jawab: question, answer |
| related[] | Topik terkait + source: parent/manual/auto |
| created_by, updated_by, created_at, updated_at | Audit siapa & kapan |
Contoh response lengkap
GET https://help.emerald-system.id/api/v1/help
?app=reservasi&form=form_reservasi&field=nama_tamu
{
"success": true,
"data": {
"id": 1, "app_name": "reservasi",
"form_segment": "form_reservasi",
"group_name": "Data Tamu", "scope": "field",
"field_name": "nama_tamu", "field_label": "Nama Tamu",
"field_type": "text", "is_required": true,
"title": "Cara mengisi Nama Tamu",
"description": "Nama lengkap tamu sesuai KTP…",
"content": "<p>Isi dengan…</p>",
"audience": "Kasir, Admin, Tamu",
"example_value": "Budi Santoso",
"image_url": "/uploads/contoh.png",
"faqs": [{ "question": "…", "answer": "…" }],
"related": [{ "title": "…", "source": "auto" }],
"created_by": "admin", "updated_by": "admin"
}
}
3 · API Public via Flutter tanpa login
Setiap endpoint di bawah jangan dipanggil manual — pakai padanannya di HelpApi (kolom kanan). Response selalu { success, data }, sudah diurus client.
| Method | Endpoint | Di Flutter | Kegunaan |
|---|---|---|---|
| GET | /api/v1/help?app=&form= &group= &scope= &grouped=1 | fetchFormHelp(app:, form:) | Daftar ringkas 1 form. &grouped=1 → pisah { global[], sections{} } (di Flutter: kelompokkan via groupName) |
| GET | /api/v1/help?app=&form=&field= | fetchFieldHelp(app:, form:, field:) | Detail lengkap 1 field: judul, isi, audience, faqs, related — untuk tombol ? |
| GET | …&field=section:x / _global | fetchSection() / fetchGlobal() | Satu bagian / seluruh layar (banner) |
| GET | /api/v1/helps/:id | fetchTopic(id) | Detail lengkap by id (termasuk faqs + related) |
| GET | /api/v1/search?q=&app= | search(q, app:) | Pencarian global (judul, keterangan, isi, audience) |
| GET | /api/v1/faqs?help_id= atau ?app=&form= | fetchFaqs(helpId: / app:, form:) | Daftar FAQ |
| GET | /api/v1/helps/:id/related | fetchRelated(id) | Bantuan terkait (manual + otomatis) |
| GET | /api/v1/apps · /api/v1/forms?app= · /api/v1/groups?app=&form= | dropdown manual* | Daftar aplikasi, form, & group (+ jumlah topik). *Belum ada di HelpApi — contoh http.get ada di Playground. |
Langkah 1 · Ambil 1 field (tombol ?)
final topic = await api.fetchFieldHelp( app: 'reservasi', form: 'form_reservasi', field: 'nama_tamu', // key tombol/field ); // topic.displayTitle — judul // topic.description — keterangan singkat // topic.content — isi lengkap // topic.audienceList — dipakai oleh siapa // topic.exampleValue — contoh benar // topic.faqs — List, topic.related — List
Langkah 2 · Tampilkan bottom sheet
if (!context.mounted) return;
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(topic.displayTitle,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 17)),
const SizedBox(height: 8),
Text(topic.description),
if (topic.exampleValue != null)
Text('Contoh: ${topic.exampleValue}'),
],
),
),
);
Langkah 3 · Section & global satu layar
// Satu bagian (mis. panel Booking Details) final section = await api.fetchFieldHelp( app: 'reservasi', form: 'form_reservasi', field: 'section:data_tamu', ); // Seluruh layar (banner penjelasan umum) final global = await api.fetchFieldHelp( app: 'reservasi', form: 'form_reservasi', field: '_global', );
Langkah 4 · Daftar + cari + related
// Semua field 1 form (tampil berkelompok via groupName)
final all = await api.fetchFormHelp(
app: 'reservasi', form: 'form_reservasi');
// Cari (untuk kolom pencarian)
final found = await api.search('kamar', app: 'reservasi');
// Bantuan terkait → buka detailnya
final rel = await api.fetchRelated(topic.id);
final next = await api.fetchTopic(rel.first.id);
4 · Widget Flutter ? + bottom sheet siap copas
Tidak ada embed web di sini — di Flutter, tombol ? = widget kecil + HelpApi.fetchFieldHelp + showModalBottomSheet. Pola persis contoh running flutter_example/lib/main.dart (label + lingkaran hijau + sheet judul/isi/FAQ/related). Butuh kode per-baris yang sudah terisi? Klik tombol </> di tabel Data Help, atau buka katalog public /help.html.
A · Tombol ? di samping label
// di samping tiap label — ganti field per baris
Row(children: [
const Text('Nama Tamu',
style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(width: 4),
HelpButton(
api: api, // HelpApi(baseUrl: 'https://help.emerald-system.id')
app: 'reservasi', form: 'form_reservasi',
field: 'nama_tamu',
),
]);
// HelpButton: lingkaran hijau ? → showHelpSheet
class HelpButton extends StatelessWidget {
const HelpButton({super.key, required this.api,
required this.app, required this.form,
required this.field});
final HelpApi api;
final String app, form, field;
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => showHelpSheet(context, api,
app: app, form: form, field: field),
customBorder: const CircleBorder(),
child: Container(width: 22, height: 22,
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Color(0xFF059669), shape: BoxShape.circle),
child: const Text('?',
style: TextStyle(color: Colors.white,
fontSize: 13, fontWeight: FontWeight.bold)),
),
);
}
}
B · Bottom sheet isi bantuan
Future<void> showHelpSheet(BuildContext context,
HelpApi api,
{required String app,
required String form,
required String field}) async {
// tampilkan loading dulu
showModalBottomSheet(
context: context,
showDragHandle: true,
builder: (_) => const Padding(
padding: EdgeInsets.all(32),
child: Center(
child: CircularProgressIndicator())));
HelpTopic topic;
try {
topic = await api.fetchFieldHelp(
app: app, form: form, field: field);
} catch (e) {
if (!context.mounted) return;
Navigator.of(context).pop(); // tutup loading
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal: $e')));
return;
}
if (!context.mounted) return;
Navigator.of(context).pop(); // tutup loading
showModalBottomSheet(
context: context,
showDragHandle: true,
isScrollControlled: true,
builder: (_) => SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(topic.displayTitle,
style: Theme.of(context)
.textTheme
.titleLarge),
if (topic.audienceList.isNotEmpty)
Wrap(spacing: 6, children: [
for (final a
in topic.audienceList)
Chip(label: Text(a)),
]),
const SizedBox(height: 8),
Text(topic.description),
for (final f in topic.faqs) ...[
const Divider(),
Text('Q: ${f.question}',
style: const TextStyle(
fontWeight:
FontWeight.bold)),
Text('A: ${f.answer}'),
],
for (final r in topic.related)
ActionChip(
label:
Text(r.displayTitle),
onPressed: () => showHelpSheet(
context, api,
app: r.appName,
form: r.formSegment,
field: r.fieldName)),
])));
}
C · 1 layar sekaligus (group + banner global)
// preload 1 form → kelompokkan via groupName
final items = await api.fetchFormHelp(
app: 'reservasi', form: 'form_reservasi');
final byGroup = <String, List<HelpSummary>>{};
for (final h in items) {
byGroup.putIfAbsent(
h.groupName ?? '(tanpa group)', () => []).add(h);
}
// banner penjelasan umum 1 layar
final global = await api.fetchGlobal(
app: 'reservasi', form: 'form_reservasi');
// satu bagian (panel Booking Details)
final section = await api.fetchSection(
app: 'reservasi', form: 'form_reservasi',
section: 'data_tamu');
D · Gambar +Render isi HTML
// image_url relatif → gabung baseUrl
String resolveUrl(String src) => src.startsWith('http')
? src
: 'https://help.emerald-system.id$src';
if (topic.imageUrl != null &&
topic.imageUrl!.isNotEmpty)
Image.network(resolveUrl(topic.imageUrl!));
// content = HTML editor (b/i/ul/a/img).
// cepat: buang tag → teks polos
String stripHtml(String? s) => (s ?? '')
.replaceAll(RegExp(r'<[^>]*>'), '').trim();
Text(stripHtml(topic.content));
// kaya (bold/list): tambah package flutter_html,
// lalu Html(data: topic.content ?? '')
5 · Setup Flutter — client, base URL, model tanpa login
UI widget-nya ada di section 4 · Widget Flutter. Di sini fondasinya: copy file flutter_example/lib/help_api.dart (client + model, hanya butuh package:http), init sekali, lalu pakai. Contoh form reservasi lengkap ada di flutter_example/lib/main.dart. Jalan pintas: katalog public /help.html — pilih segment, double-click kartu, langsung copas kode yang sudah terisi app/form/field-nya.
1 · Dependensi + init
# pubspec.yaml flutter pub add http // main.dart — production import 'help_api.dart'; final api = HelpApi(baseUrl: 'https://help.emerald-system.id'); // lokal: emulator http://10.0.2.2:8750, iOS http://localhost:8750
2 · Tombol ? ambil detail + related
Future<void> showHelp(BuildContext context) async {
final topic = await api.fetchFieldHelp(
app: 'reservasi', form: 'form_reservasi',
field: 'nama_tamu', // group ikut terkirim
);
// topic.displayTitle, topic.groupName, topic.scope,
// topic.description, topic.content,
// topic.audienceList, topic.exampleValue,
// topic.faqs, topic.related
}
Model Dart ← API (semua core data terpetakan)
// help_api.dart — tanpa login, hanya package:http
HelpTopic t = ...;
t.displayTitle; // title → field_label → field_name
t.groupName; // group · t.scope; // field|section|global
t.description; // keterangan · t.content; // HTML isi
t.audienceList; // ['Kasir','Admin'] · t.exampleValue;
t.imageUrl; // '/uploads/x.png' → Image.network(resolveUrl(...))
t.faqs; // List<HelpFaq> {question, answer}
t.related; // List<HelpRelated> {…sama + source}
t.createdBy; t.updatedBy; // audit siapa & kapan
Wajib: tangani loading & error
try {
final topic = await api.fetchFieldHelp(
app: 'reservasi', form: 'form_reservasi', field: 'nama_tamu');
if (!context.mounted) return; // wajib setelah await
showModalBottomSheet(/* ... tampilkan topic ... */);
} on HelpApiException catch (e) {
// e.statusCode 404 = key salah / belum diinput dashboard
// e.message 'Timeout...' / 'Tidak dapat menghubungi...' = server mati
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Gagal: ${e.message}')));
}
6 · API Admin butuh login
Khusus dashboard/internal — aplikasi Flutter user TIDAK memanggil ini (user pakai section 3–5 yang tanpa login). Header wajib: Authorization: Bearer <token> — token dari POST /api/login. Kelola user khusus admin (.env).
| Method | Endpoint | Kegunaan |
|---|---|---|
| POST | /api/login | { username, password } → { token } |
| GET | /api/admin/helps?search=&app=&form= | List (+ cari) |
| CRUD | /api/admin/helps · /api/admin/helps/:id | Tambah / detail(+faq+related) / ubah / hapus |
| CRUD | /api/admin/faqs · /api/admin/faqs/:id | Kelola FAQ (GET list: /helps/:id/faqs) |
| POST/DEL | /api/admin/helps/:id/related | Tambah/hapus relasi manual { related_id } |
| POST | /api/admin/upload | Upload gambar (multipart field image, png/jpg/webp/gif/svg ≤2MB) → { url } |
| CRUD | /api/admin/users (admin saja) | Tambah user { username, password, name } |
Contoh Flutter (Dart, package:http) — khusus tool internal
// 1) login → token (simpan di secure storage, JANGAN hardcode)
import 'package:http/http.dart' as http;
import 'dart:convert';
const base = 'https://help.emerald-system.id';
final login = await http.post(Uri.parse('$base/api/login'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(
{'username': 'admin', 'password': '***'}));
final token =
(jsonDecode(login.body) as Map)['token'] as String;
// 2) list + tambah help (header Bearer wajib)
final list = await http.get(
Uri.parse('$base/api/admin/helps?app=reservasi'),
headers: {'Authorization': 'Bearer $token'});
final created = await http.post(
Uri.parse('$base/api/admin/helps'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token'
},
body: jsonEncode({
'app_name': 'reservasi',
'form_segment': 'form_reservasi',
'scope': 'field',
'field_name': 'nama_tamu',
'title': 'Cara mengisi Nama Tamu'
}));
7 · Playground — coba langsung + salin Flutter
Memakai data asli database. Pilih mode, isi parameter, klik Jalankan — lalu klik Salin sebagai kode Flutter untuk dapat potongan HelpApi yang setara.
// hasil akan tampil di sini…
// potongan HelpApi yang setara akan tampil di sini…