Skip to content

Commit 1b0a950

Browse files
committed
feat: enhance recent apps feature with localization and improved UI handling
1 parent 297933a commit 1b0a950

8 files changed

Lines changed: 155 additions & 117 deletions

File tree

apps/console/src/index.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
/* Scan sources for Tailwind classes */
44
@source '../src/**/*.{ts,tsx}';
5+
@source '../../../packages/app-shell/src/**/*.{ts,tsx}';
6+
@source '../../../packages/auth/src/**/*.{ts,tsx}';
57
@source '../../../packages/components/src/**/*.{ts,tsx}';
68
@source '../../../packages/layout/src/**/*.{ts,tsx}';
79
@source '../../../packages/fields/src/**/*.{ts,tsx}';
@@ -20,6 +22,11 @@
2022
@source '../../../packages/plugin-chatbot/src/**/*.{ts,tsx}';
2123
@source '../../../packages/plugin-editor/src/**/*.{ts,tsx}';
2224
@source '../../../packages/plugin-view/src/**/*.{ts,tsx}';
25+
@source '../../../packages/plugin-detail/src/**/*.{ts,tsx}';
26+
@source '../../../packages/plugin-list/src/**/*.{ts,tsx}';
27+
@source '../../../packages/plugin-designer/src/**/*.{ts,tsx}';
28+
@source '../../../packages/i18n/src/**/*.{ts,tsx}';
29+
@source '../../../packages/collaboration/src/**/*.{ts,tsx}';
2330
@source '../../../packages/mobile/src/**/*.{ts,tsx}';
2431

2532
/* Tailwind plugin for animations remove

packages/app-shell/src/console/home/AppCard.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ export function AppCard({ app, onClick, isFavorite }: AppCardProps) {
5353
size="sm"
5454
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity"
5555
onClick={handleToggleFavorite}
56-
aria-label={isFavorite ? `Remove ${label} from favorites` : `Add ${label} to favorites`}
56+
aria-label={isFavorite
57+
? t('common.removeFromFavorites', { defaultValue: 'Remove from favorites' }) + ` — ${label}`
58+
: t('common.addToFavorites', { defaultValue: 'Add to favorites' }) + ` — ${label}`}
5759
aria-pressed={isFavorite}
5860
data-testid={`favorite-btn-${app.name}`}
5961
>

packages/app-shell/src/console/home/RecentApps.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export function RecentApps({ items }: RecentAppsProps) {
3535
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
3636
{items.map((item) => {
3737
const Icon = getIcon(item.type);
38+
const typeLabel = t(`home.recentApps.itemType.${item.type}`, {
39+
defaultValue: capitalizeFirst(item.type),
40+
});
3841
return (
3942
<Card
4043
key={item.id}
@@ -57,7 +60,7 @@ export function RecentApps({ items }: RecentAppsProps) {
5760
</div>
5861
<div className="flex-1 min-w-0">
5962
<h3 className="font-medium text-sm truncate">{item.label}</h3>
60-
<p className="text-xs text-muted-foreground">{capitalizeFirst(item.type)}</p>
63+
<p className="text-xs text-muted-foreground">{typeLabel}</p>
6164
</div>
6265
</div>
6366
</CardContent>

packages/app-shell/src/layout/AppHeader.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,9 @@ function PathSep() {
7272
}
7373

7474

75-
const FALLBACK_PRESENCE_USERS: PresenceUser[] = [
76-
{ userId: 'u1', userName: 'Alice Chen', color: '#3498db', status: 'active', lastActivity: new Date().toISOString() },
77-
{ userId: 'u2', userName: 'Bob Smith', color: '#2ecc71', status: 'idle', lastActivity: new Date().toISOString() },
78-
{ userId: 'u3', userName: 'Carol Li', color: '#e74c3c', status: 'active', lastActivity: new Date().toISOString() },
79-
];
75+
// No fake fallback presence — render nothing when the API has no data so the
76+
// header doesn't ship phantom collaborators in production.
77+
const EMPTY_PRESENCE_USERS: PresenceUser[] = [];
8078

8179
export type AppHeaderVariant = 'app' | 'home' | 'orgs';
8280

@@ -148,7 +146,7 @@ export function AppHeader({
148146

149147
useEffect(() => { fetchPresenceAndActivities(); }, [fetchPresenceAndActivities]);
150148

151-
const activeUsers = presenceUsers ?? apiPresenceUsers ?? FALLBACK_PRESENCE_USERS;
149+
const activeUsers = presenceUsers ?? apiPresenceUsers ?? EMPTY_PRESENCE_USERS;
152150
const activeActivities = activities ?? apiActivities ?? [];
153151
const orgList = organizations ?? [];
154152
const hasOrgSection = isOrganizationsLoading || orgList.length > 0 || !!activeOrganization;

packages/i18n/src/locales/en.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,15 @@ const en = {
884884
systemSettings: 'System Settings',
885885
systemSettingsDesc: 'Configure your workspace',
886886
},
887+
recentApps: {
888+
title: 'Recently Accessed',
889+
itemType: {
890+
object: 'Object',
891+
dashboard: 'Dashboard',
892+
page: 'Page',
893+
record: 'Record',
894+
},
895+
},
887896
appCard: {
888897
noDescription: 'No description',
889898
default: 'Default',

packages/i18n/src/locales/zh.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,15 @@ const zh = {
884884
systemSettings: '系统设置',
885885
systemSettingsDesc: '配置您的工作区',
886886
},
887+
recentApps: {
888+
title: '最近访问',
889+
itemType: {
890+
object: '对象',
891+
dashboard: '仪表盘',
892+
page: '页面',
893+
record: '记录',
894+
},
895+
},
887896
appCard: {
888897
noDescription: '暂无描述',
889898
default: '默认',

packages/plugin-detail/src/DetailSection.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,6 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
275275
<div className="flex items-center gap-2">
276276
{section.icon && <span className="text-muted-foreground">{section.icon}</span>}
277277
<span>{section.title}</span>
278-
{section.fields && (
279-
<Badge variant="secondary" className="ml-2 text-xs">
280-
{section.fields.length}
281-
</Badge>
282-
)}
283278
</div>
284279
</CardTitle>
285280
{section.description && (

packages/plugin-detail/src/DetailView.tsx

Lines changed: 119 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -683,123 +683,138 @@ export const DetailView: React.FC<DetailViewProps> = ({
683683
<HeaderHighlight fields={schema.highlightFields} data={data} objectName={schema.objectName} objectSchema={objectSchema} />
684684
)}
685685

686-
{/* Auto Tabs mode: wrap sections, related, activity into tabs */}
687-
{schema.autoTabs && !schema.tabs?.length ? (
688-
<Tabs defaultValue="details" className="w-full">
689-
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0">
690-
<TabsTrigger
691-
value="details"
692-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
693-
>
694-
{t('detail.details')}
695-
</TabsTrigger>
696-
{effectiveRelated.length > 0 && (
697-
<TabsTrigger
698-
value="related"
699-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
700-
>
701-
<span className="flex items-center gap-1.5">
702-
{t('detail.related')}
703-
<Badge variant="secondary" className="text-xs">{effectiveRelated.length}</Badge>
704-
</span>
705-
</TabsTrigger>
706-
)}
707-
{schema.activities && schema.activities.length > 0 && (
708-
<TabsTrigger
709-
value="activity"
710-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
711-
>
712-
<span className="flex items-center gap-1.5">
713-
{t('detail.activity')}
714-
<Badge variant="secondary" className="text-xs">{schema.activities.length}</Badge>
715-
</span>
716-
</TabsTrigger>
686+
{/* Auto Tabs mode: wrap sections, related, activity into tabs.
687+
When only the Details tab would render (no related, no activity),
688+
skip the Tabs strip entirely — it's pure visual noise. */}
689+
{schema.autoTabs && !schema.tabs?.length ? (() => {
690+
const hasRelated = effectiveRelated.length > 0;
691+
const hasActivity = !!schema.activities && schema.activities.length > 0;
692+
const detailsContent = (
693+
<div className="space-y-3 sm:space-y-4">
694+
{/* Section Groups */}
695+
{schema.sectionGroups && schema.sectionGroups.length > 0 && (
696+
schema.sectionGroups.map((group, index) => (
697+
<SectionGroup
698+
key={index}
699+
group={group}
700+
data={{ ...data, ...editedValues }}
701+
objectSchema={objectSchema}
702+
objectName={schema.objectName}
703+
isEditing={isInlineEditing}
704+
onFieldChange={handleInlineFieldChange}
705+
/>
706+
))
717707
)}
718-
</TabsList>
719-
720-
{/* Details Tab Content */}
721-
<TabsContent value="details" className="mt-4">
722-
<div className="space-y-3 sm:space-y-4">
723-
{/* Section Groups */}
724-
{schema.sectionGroups && schema.sectionGroups.length > 0 && (
725-
schema.sectionGroups.map((group, index) => (
726-
<SectionGroup
727-
key={index}
728-
group={group}
729-
data={{ ...data, ...editedValues }}
730-
objectSchema={objectSchema}
731-
objectName={schema.objectName}
732-
isEditing={isInlineEditing}
733-
onFieldChange={handleInlineFieldChange}
734-
/>
735-
))
736-
)}
737-
{schema.sections && schema.sections.length > 0 && (
738-
schema.sections.map((section, index) => (
739-
<DetailSection
740-
key={index}
741-
section={section}
742-
data={{ ...data, ...editedValues }}
743-
objectSchema={objectSchema}
744-
objectName={schema.objectName}
745-
isEditing={isInlineEditing}
746-
onFieldChange={handleInlineFieldChange}
747-
/>
748-
))
749-
)}
750-
{schema.fields && schema.fields.length > 0 && !schema.sections?.length && (
708+
{schema.sections && schema.sections.length > 0 && (
709+
schema.sections.map((section, index) => (
751710
<DetailSection
752-
section={{
753-
fields: schema.fields,
754-
columns: schema.columns,
755-
}}
711+
key={index}
712+
section={section}
756713
data={{ ...data, ...editedValues }}
757714
objectSchema={objectSchema}
758715
objectName={schema.objectName}
759716
isEditing={isInlineEditing}
760717
onFieldChange={handleInlineFieldChange}
761718
/>
719+
))
720+
)}
721+
{schema.fields && schema.fields.length > 0 && !schema.sections?.length && (
722+
<DetailSection
723+
section={{
724+
fields: schema.fields,
725+
columns: schema.columns,
726+
}}
727+
data={{ ...data, ...editedValues }}
728+
objectSchema={objectSchema}
729+
objectName={schema.objectName}
730+
isEditing={isInlineEditing}
731+
onFieldChange={handleInlineFieldChange}
732+
/>
733+
)}
734+
{/* Comments in details tab */}
735+
{schema.comments && (
736+
<RecordComments
737+
comments={schema.comments}
738+
onAddComment={schema.onAddComment}
739+
/>
740+
)}
741+
</div>
742+
);
743+
744+
if (!hasRelated && !hasActivity) {
745+
// Single-tab case: render just the details content without a tab strip.
746+
return <div className="mt-2">{detailsContent}</div>;
747+
}
748+
749+
return (
750+
<Tabs defaultValue="details" className="w-full">
751+
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0">
752+
<TabsTrigger
753+
value="details"
754+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
755+
>
756+
{t('detail.details')}
757+
</TabsTrigger>
758+
{hasRelated && (
759+
<TabsTrigger
760+
value="related"
761+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
762+
>
763+
<span className="flex items-center gap-1.5">
764+
{t('detail.related')}
765+
<Badge variant="secondary" className="text-xs">{effectiveRelated.length}</Badge>
766+
</span>
767+
</TabsTrigger>
762768
)}
763-
{/* Comments in details tab */}
764-
{schema.comments && (
765-
<RecordComments
766-
comments={schema.comments}
767-
onAddComment={schema.onAddComment}
768-
/>
769+
{hasActivity && (
770+
<TabsTrigger
771+
value="activity"
772+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
773+
>
774+
<span className="flex items-center gap-1.5">
775+
{t('detail.activity')}
776+
<Badge variant="secondary" className="text-xs">{schema.activities!.length}</Badge>
777+
</span>
778+
</TabsTrigger>
769779
)}
770-
</div>
771-
</TabsContent>
780+
</TabsList>
772781

773-
{/* Related Tab Content */}
774-
{effectiveRelated.length > 0 && (
775-
<TabsContent value="related" className="mt-4">
776-
<div className="space-y-4">
777-
{effectiveRelated.map((related, index) => (
778-
<RelatedList
779-
key={index}
780-
title={related.title}
781-
type={related.type}
782-
api={related.api}
783-
data={related.data}
784-
columns={related.columns as any}
785-
dataSource={dataSource}
786-
objectName={related.api}
787-
collapsible
788-
pageSize={DEFAULT_RELATED_PAGE_SIZE}
789-
/>
790-
))}
791-
</div>
782+
{/* Details Tab Content */}
783+
<TabsContent value="details" className="mt-4">
784+
{detailsContent}
792785
</TabsContent>
793-
)}
794786

795-
{/* Activity Tab Content */}
796-
{schema.activities && schema.activities.length > 0 && (
797-
<TabsContent value="activity" className="mt-4">
798-
<ActivityTimeline activities={schema.activities} />
799-
</TabsContent>
800-
)}
801-
</Tabs>
802-
) : (
787+
{/* Related Tab Content */}
788+
{hasRelated && (
789+
<TabsContent value="related" className="mt-4">
790+
<div className="space-y-4">
791+
{effectiveRelated.map((related, index) => (
792+
<RelatedList
793+
key={index}
794+
title={related.title}
795+
type={related.type}
796+
api={related.api}
797+
data={related.data}
798+
columns={related.columns as any}
799+
dataSource={dataSource}
800+
objectName={related.api}
801+
collapsible
802+
pageSize={DEFAULT_RELATED_PAGE_SIZE}
803+
/>
804+
))}
805+
</div>
806+
</TabsContent>
807+
)}
808+
809+
{/* Activity Tab Content */}
810+
{hasActivity && (
811+
<TabsContent value="activity" className="mt-4">
812+
<ActivityTimeline activities={schema.activities!} />
813+
</TabsContent>
814+
)}
815+
</Tabs>
816+
);
817+
})() : (
803818
<>
804819
{/* Section Groups */}
805820
{schema.sectionGroups && schema.sectionGroups.length > 0 && (

0 commit comments

Comments
 (0)