b307f66f2c
Projects: Brick-backed model with list/detail screens and nav entry. Work Logs: per-contributor time ledger, assignee running-clock + helper logged minutes with Hours/Minutes input; pause-after-save and helper-deduct prompts. Comments: threaded comments with file attachments and Quill composer. Detail screen: tabbed layout (Work Log / Comments / Activity), editable title + description via edit dialog, Quill-rendered descriptions with legacy plain-text fallback. Create dialog: fixed-width (480px max), Quill description editor. Shared QuillFieldEditor + QuillReadOnly widgets extracted for reuse. RLS: hardened activity-log actor binding, immutable creator_id/ created_at triggers, SECURITY INVOKER visibility helper, append-only work-log and scoped comment policies. Brick migration for project_id FK on programmer_tasks. Co-Authored-By: claude-flow <ruv@ruv.net>
81 lines
2.2 KiB
Dart
81 lines
2.2 KiB
Dart
import 'package:brick_offline_first_with_supabase/brick_offline_first_with_supabase.dart';
|
|
import 'package:brick_sqlite/brick_sqlite.dart';
|
|
import 'package:brick_supabase/brick_supabase.dart';
|
|
|
|
import '../utils/app_time.dart';
|
|
|
|
/// Status of a programmer project.
|
|
class ProgrammerProjectStatus {
|
|
static const active = 'active';
|
|
static const archived = 'archived';
|
|
|
|
static const all = [active, archived];
|
|
|
|
static String label(String status) {
|
|
switch (status) {
|
|
case active:
|
|
return 'Active';
|
|
case archived:
|
|
return 'Archived';
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
@ConnectOfflineFirstWithSupabase(
|
|
supabaseConfig: SupabaseSerializable(tableName: 'programmer_projects'),
|
|
)
|
|
class ProgrammerProject extends OfflineFirstWithSupabaseModel {
|
|
final String id;
|
|
final String name;
|
|
final String? description;
|
|
final String status;
|
|
final String? creatorId;
|
|
final DateTime createdAt;
|
|
final DateTime updatedAt;
|
|
|
|
ProgrammerProject({
|
|
required this.id,
|
|
required this.name,
|
|
this.description,
|
|
required this.status,
|
|
this.creatorId,
|
|
required this.createdAt,
|
|
required this.updatedAt,
|
|
});
|
|
|
|
bool get isArchived => status == ProgrammerProjectStatus.archived;
|
|
|
|
@Supabase(ignore: true)
|
|
@Sqlite(ignore: true)
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is ProgrammerProject &&
|
|
runtimeType == other.runtimeType &&
|
|
id == other.id &&
|
|
name == other.name &&
|
|
status == other.status &&
|
|
updatedAt == other.updatedAt;
|
|
|
|
@Supabase(ignore: true)
|
|
@Sqlite(ignore: true)
|
|
@override
|
|
int get hashCode => Object.hash(id, name, status, updatedAt);
|
|
|
|
factory ProgrammerProject.fromMap(Map<String, dynamic> map) {
|
|
return ProgrammerProject(
|
|
id: map['id'].toString(),
|
|
name: map['name'] as String? ?? '',
|
|
description: map['description'] as String?,
|
|
status: map['status'] as String? ?? ProgrammerProjectStatus.active,
|
|
creatorId: map['creator_id']?.toString(),
|
|
createdAt: AppTime.parse(map['created_at'] as String),
|
|
updatedAt: map['updated_at'] == null
|
|
? AppTime.parse(map['created_at'] as String)
|
|
: AppTime.parse(map['updated_at'] as String),
|
|
);
|
|
}
|
|
}
|